Search
Search

Transaction: Vu1HUPb...82YH

Receiver
Status
Failed
Transaction Fee
0.02635 
Deposit Value
3.12 
Gas Used
263 Tgas
Attached Gas
300 Tgas
Created
May 21, 2024 at 11:54:25am
Hash
Vu1HUPbdTe4ocSSossYsrcuQ4LLLbk6BfXfaf3R82YH

Actions

Called method: 'set' in contract: social.near
Arguments:
{ "data": { "devhub.megha19.near": { "widget": { "devhub.components.molecule.CommunityControl": { "": "const { className, title, icon, href, onClick } = props;\n\nconst Button = styled.button`\n display: flex;\n align-items: center;\n gap: 8px;\n\n border-radius: 4px;\n background: #04a46e;\n\n color: #f4f4f4;\n font-size: 16px;\n font-style: normal;\n font-weight: 700;\n line-height: 20px; /* 125% */\n\n padding: 0.5rem 1rem;\n\n &:hover {\n background: #555555;\n text-decoration: none !important;\n }\n\n outline: none;\n border: none;\n`;\n\nreturn (\n <div className={`d-flex flex-row-reverse ${props.className}`}>\n {props.href ? (\n <Link to={props.href} style={{ textDecoration: \"none\" }}>\n <Button\n className=\"community-control\"\n data-testid={props.testId ? props.testId : \"\"}\n >\n <i className={props.icon ? props.icon : \"bi bi-plus-circle-fill\"}></i>\n {props.title}\n </Button>\n </Link>\n ) : (\n <Button\n onClick={props.onClick}\n className=\"community-control\"\n data-testid={props.testId ? props.testId : \"\"}\n >\n <i className={props.icon ? props.icon : \"bi bi-plus-circle-fill\"}></i>\n {props.title || \"Community\"}\n </Button>\n )}\n </div>\n);\n" }, "devhub.components.molecule.Input": { "": "const TextInput = ({\n className,\n format,\n inputProps: { className: inputClassName, ...inputProps },\n key,\n label,\n multiline,\n onChange,\n placeholder,\n type,\n value,\n skipPaddingGap,\n style,\n error,\n ...otherProps\n}) => {\n State.init({\n data: value,\n error: error,\n });\n\n useEffect(() => {\n const inputError = \"\";\n if (value !== state.data) {\n // check for input number error (since type: number doesn't work on firefox/safari)\n if (inputProps.inputmode === \"numeric\") {\n const inputValue = state.data;\n if (!inputValue) {\n return;\n }\n let isValidInteger = /^[1-9][0-9]*$/.test(inputValue);\n if (!isValidInteger) {\n inputError = \"Please enter the nearest positive whole number.\";\n }\n State.update({ error: inputError });\n }\n const handler = setTimeout(() => {\n onChange({ target: { value: state.data }, error: inputError });\n }, 30);\n\n return () => {\n clearTimeout(handler);\n };\n }\n }, [state.data]);\n\n useEffect(() => {\n if (value !== state.data) {\n State.update({ data: value });\n }\n }, [value]);\n\n useEffect(() => {\n if (error !== state.error) {\n State.update({ error: error });\n }\n }, [error]);\n\n const typeAttribute =\n type === \"text\" ||\n type === \"password\" ||\n type === \"number\" ||\n type === \"date\"\n ? type\n : \"text\";\n\n const isValid = () => {\n if (!state.data || state.data.length === 0) {\n return !inputProps.required;\n } else if (inputProps.min && inputProps.min > state.data?.length) {\n return false;\n } else if (inputProps.max && inputProps.max < state.data?.length) {\n return false;\n } else if (\n inputProps.allowCommaAndSpace === false &&\n /^[^,\\s]*$/.test(state.data) === false\n ) {\n return false;\n } else if (\n inputProps.validUrl === true &&\n /^(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)$/.test(\n state.data\n ) === false\n ) {\n return false;\n }\n return true;\n };\n\n const renderedLabels = [\n (label?.length ?? 0) > 0 ? (\n <span className=\"d-inline-flex gap-1 text-wrap\">\n <span>{label}</span>\n\n {inputProps.required ? <span className=\"text-danger\">*</span> : null}\n </span>\n ) : null,\n\n format === \"markdown\" ? (\n <i class=\"bi bi-markdown text-muted\" title=\"Markdown\" />\n ) : null,\n\n format === \"comma-separated\" ? (\n <span\n className={`d-inline-flex align-items-center ${\n isValid() ? \"text-muted\" : \"text-danger\"\n }`}\n style={{ fontSize: 12 }}\n >\n {format}\n </span>\n ) : null,\n\n (inputProps.max ?? null) !== null ? (\n <span\n className={`d-inline-flex ${isValid() ? \"text-muted\" : \"text-danger\"}`}\n style={{ fontSize: 12 }}\n >{`${state.data?.length ?? 0} / ${inputProps.max}`}</span>\n ) : null,\n ].filter((label) => label !== null);\n\n const onKeyDown = props.onKeyDown ?? (() => {});\n\n return (\n <div\n className={[\n \"d-flex flex-column flex-1 align-items-start justify-content-evenly\",\n skipPaddingGap ? \"\" : \"gap-1 p-2\",\n className ?? \"\",\n ].join(\" \")}\n style={style}\n {...otherProps}\n >\n {renderedLabels.length > 0 ? (\n <span\n className=\"d-flex justify-content-between align-items-center gap-3 w-100\"\n id={key}\n >\n {renderedLabels.map((label) => label)}\n </span>\n ) : null}\n\n {!multiline ? (\n <div className=\"w-100\">\n <div className=\"input-group\">\n {inputProps.prefix && (\n <span className=\"input-group-text bg-white border-end-0\">\n {inputProps.prefix}\n </span>\n )}\n <input\n aria-describedby={key}\n data-testid={key}\n aria-label={label}\n className={[\n \"form-control border\",\n inputClassName,\n inputProps.prefix ? \"border-start-0\" : \"\",\n ].join(\" \")}\n type={typeAttribute}\n maxLength={inputProps.max}\n value={state.data}\n onChange={(e) => State.update({ data: e.target.value })}\n onBlur={(e) => {\n if (props.onBlur) {\n onBlur({ target: { value: e.target.value } });\n }\n }}\n onKeyDown={onKeyDown}\n {...{ placeholder, ...inputProps }}\n />\n </div>\n {state.error && (\n <div style={{ color: \"red\" }} className=\"text-sm\">\n {state.error}\n </div>\n )}\n </div>\n ) : (\n <textarea\n aria-describedby={key}\n data-testid={key}\n aria-label={label}\n className={[\"form-control border\", inputClassName].join(\" \")}\n placeholder={\n placeholder + (inputProps.required ? \" ( required )\" : \"\")\n }\n style={{ resize: inputProps.resize ?? \"vertical\" }}\n type={typeAttribute}\n maxLength={inputProps.max}\n value={state.data}\n onChange={(e) => State.update({ data: e.target.value })}\n onBlur={(e) => {\n if (props.onBlur) {\n onBlur({ target: { value: e.target.value } });\n }\n }}\n onKeyDown={onKeyDown}\n {...{ placeholder, ...inputProps }}\n />\n )}\n </div>\n );\n};\n\nreturn TextInput(props);\n" }, "devhub.entity.proposal.CommentIcon": { "": "const item = props.item;\nconst showOverlay = props.showOverlay ?? true;\n\nif (!props.hideCount && !item) {\n return \"\";\n}\n\nconst comments = !props.hideCount && Social.index(\"comment\", item);\nconst dataLoading = props.hideCount ? false : comments === null;\nconst totalComments = comments?.length || 0;\n\nconst CommentButton = styled.button`\n border: 0;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n color: #687076;\n font-weight: 400;\n font-size: 14px;\n line-height: 17px;\n cursor: pointer;\n background: none;\n padding: 6px;\n transition: color 200ms;\n\n i {\n font-size: 16px;\n transition: color 200ms;\n }\n\n &:hover,\n &:focus {\n outline: none;\n color: #11181c;\n }\n`;\n\nreturn (\n <CommentButton\n disabled={dataLoading || !context.accountId}\n title={showOverlay && \"Add Comment\"}\n onClick={props.onClick}\n >\n <i className=\"bi-chat\" />\n {!props.hideCount && totalComments}\n </CommentButton>\n);\n" }, "devhub.entity.team.LabelPermissions": { "": "const { identifier, editPost, setEditPost, useLabels, setUseLabels, disabled } =\n props;\n\nreturn (\n <>\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n value={editPost}\n id={`editPostCheckbox${identifier}`}\n checked={editPost}\n onChange={() => setEditPost(!editPost)}\n disabled={disabled}\n />\n <label class=\"form-check-label\" for={`editPostCheckbox${identifier}`}>\n This team is allowed to edit-post with this / these labels\n </label>\n </div>\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n value={useLabels}\n id={`useLabelsCheckbox${identifier}`}\n checked={useLabels}\n onChange={() => setUseLabels(!useLabels)}\n disabled={disabled}\n />\n <label class=\"form-check-label\" for={`useLabelsCheckbox${identifier}`}>\n Only this team and moderators are allowed to use this label\n </label>\n </div>\n </>\n);\n" }, "devhub.entity.addon.blog.Page": { "": "const { getAccountCommunityPermissions } = VM.require(\n \"devhub.megha19.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.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"\n }\n props={{ text: content }}\n />\n </Container>\n </>\n );\n}\n\nreturn { Page };\n" }, "devhub.page.admin.index": { "": "const { hasModerator, getRootMembers, getAccessControlInfo } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nif (!hasModerator || !getRootMembers || !getAccessControlInfo) {\n return <p>Loading modules...</p>;\n}\n\nconst accessControlInfo = getAccessControlInfo();\n\nif (!accessControlInfo.members_list) {\n return <p>Loading members list...</p>;\n}\n\nconst rootMembers = getRootMembers();\nconst teamNames = Object.keys(rootMembers || {});\n\nconst isDevHubModerator = hasModerator({\n account_id: context.accountId,\n});\n\nconst noPermissionBanner = (\n <div\n className=\"d-flex flex-column justify-content-center align-items-center\"\n style={{ height: 384 }}\n >\n <h2 className=\"alert alert-danger\">\n Your account does not have administration permissions.\n </h2>\n </div>\n);\n\nif (!isDevHubModerator) {\n return noPermissionBanner;\n}\n\nfunction createEditTeam({\n teamName,\n label,\n editPost,\n useLabels,\n members,\n contractCall, // typescript edit_member || add_member\n}) {\n let txn = [];\n console.log(\"🚀 ~ file: index.jsx:48 ~ teamNames:\", teamNames);\n if (teamNames.includes(`team:${teamName}`) && contractCall === \"add_member\") {\n return setAlertMessage(\"This team name already exists\");\n }\n const allLabels = Object.keys(accessControlInfo.rules_list);\n if (allLabels.includes(label) && contractCall === \"add_member\") {\n return setAlertMessage(\"This label is already restricted by another team\");\n }\n\n const membersAndTeams = Object.keys(accessControlInfo.members_list);\n members.forEach((member) => {\n // if Contract panic member does not exist in the members_list\n if (!membersAndTeams.includes(member)) {\n // Add member\n txn.push({\n contractName: \"devhub.near\",\n methodName: \"add_member\",\n args: {\n member: member,\n metadata: {\n member_metadata_version: \"V0\",\n description: \"\",\n permissions: {},\n children: [],\n parents: [],\n },\n },\n gas: Big(10).pow(14),\n });\n }\n });\n\n // Check edit team\n Near.call([\n ...txn,\n {\n contractName: \"devhub.near\",\n methodName: contractCall, // add_member || edit_member\n args: {\n member: `team:${teamName}`,\n metadata: {\n member_metadata_version: \"V0\",\n description: \"\",\n permissions: {\n [label]: [\n ...(editPost ? [\"edit-post\"] : []),\n ...(useLabels ? [\"use-labels\"] : []),\n ],\n },\n children: members,\n parents: [],\n },\n },\n gas: Big(10).pow(14),\n },\n ]);\n}\n\nconst Container = styled.div`\n width: 100%;\n margin: 0 auto;\n padding: 20px;\n text-align: left;\n`;\n\nconst Tab = styled.button`\n color: rgb(0, 236, 151);\n &:hover {\n color: rgba(0, 236, 151, 0.5);\n }\n`;\n\nreturn (\n <Container>\n <div className=\"d-flex flex-column gap-4 p-4\">\n <ul class=\"nav nav-tabs\" id=\"myTab\" role=\"tablist\">\n <li class=\"nav-item\" role=\"presentation\">\n <Tab\n className=\"nav-link active\"\n id=\"home-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#home\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"home\"\n aria-selected=\"true\"\n >\n Home page settings\n </Tab>\n </li>\n <li class=\"nav-item\" role=\"presentation\">\n <Tab\n className=\"nav-link\"\n id=\"profile-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#profile\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"profile\"\n aria-selected=\"false\"\n >\n Moderators\n </Tab>\n </li>\n <li class=\"nav-item\" role=\"presentation\">\n <Tab\n className=\"nav-link\"\n id=\"contact-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#contact\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"contact\"\n aria-selected=\"false\"\n >\n Restricted labels\n </Tab>\n </li>\n </ul>\n <div class=\"tab-content\" id=\"myTabContent\">\n <div\n class=\"tab-pane fade show active\"\n id=\"home\"\n role=\"tabpanel\"\n aria-labelledby=\"home-tab\"\n >\n <Widget src=\"devhub.megha19.near/widget/devhub.page.admin.homepageTab\" />\n </div>\n <div\n class=\"tab-pane fade\"\n id=\"profile\"\n role=\"tabpanel\"\n aria-labelledby=\"profile-tab\"\n >\n <Widget\n src=\"devhub.megha19.near/widget/devhub.page.admin.moderatorsTab\"\n props={{\n accessControlInfo,\n createEditTeam,\n }}\n />\n </div>\n <div\n class=\"tab-pane fade\"\n id=\"contact\"\n role=\"tabpanel\"\n aria-labelledby=\"contact-tab\"\n >\n <Widget\n src=\"devhub.megha19.near/widget/devhub.page.admin.restrictedLabelsTab\"\n props={{\n accessControlInfo,\n createEditTeam,\n teamNames,\n }}\n />\n </div>\n </div>\n </div>\n </Container>\n);\n" }, "devhub.page.proposals": { "": "return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.Feed\"}\n props={{}}\n />\n);\n" }, "devhub.components.atom.Icon": { "": "const svgIconsByVariant = {\n floppy_drive: (elementProps) => (\n <svg\n fill=\"#ffffff\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16px\"\n height=\"16px\"\n viewBox=\"0 0 353.073 353.073\"\n {...elementProps}\n >\n <g>\n <path\n d={[\n \"M340.969,0H12.105C5.423,0,0,5.423,0,12.105v328.863c0,6.68,5.423,12.105,12.105,12.105h328.864\",\n \"c6.679,0,12.104-5.426,12.104-12.105V12.105C353.073,5.423,347.647,0,340.969,0z\",\n \"M67.589,18.164h217.895v101.884H67.589V18.164z\",\n \"M296.082,327.35H57.003V176.537h239.079V327.35z\",\n \"M223.953,33.295h30.269v72.638h-30.269V33.295z\",\n \"M274.135,213.863H78.938v-12.105\",\n \"h195.197V213.863z\",\n \"M274.135,256.231H78.938v-12.105h195.197V256.231z\",\n \"M274.135,297.087H78.938v-12.105h195.197V297.087z\",\n ].join(\" \")}\n />\n </g>\n </svg>\n ),\n};\n\nconst iconsByType = {\n bootstrap_icon: ({ className, variant, ...otherProps }) => (\n <i className={`bi ${variant} ${className}`} {...otherProps} />\n ),\n\n svg_icon: ({ variant, ...elementProps }) =>\n svgIconsByVariant[variant](elementProps),\n};\n\nconst Icon = ({ type, ...otherProps }) =>\n typeof iconsByType[type] === \"function\"\n ? iconsByType[type](otherProps)\n : null;\n\nreturn Icon(props);\n" }, "devhub.entity.post.Postv2": { "": "const { getPost } =\n VM.require(\"devhub.megha19.near/widget/core.adapter.devhub-contract\") ||\n (() => {});\n\nconst { postKey, template } = props;\n\nconst post = getPost({ post_id: parseInt(postKey) });\n\nif (!post) {\n return <div>Loading ...</div>;\n}\n\nconst Template = template || (() => <></>);\n\nreturn (\n <Template\n labels={post.snapshot.labels}\n data={JSON.parse(post.snapshot.description || \"null\") || {}}\n />\n);\n" }, "devhub.components.molecule.ProfileLine": { "": "const accountId = props.accountId ?? context.accountId;\n\nreturn (\n <span>\n <Widget\n src=\"mob.near/widget/ProfileLine\"\n props={{\n ...props,\n accountId,\n link: `#/mob.near/widget/ProfilePage?accountId=${accountId}`,\n }}\n />\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.BadgesList\"\n props={{\n accountId,\n mode: \"compact\",\n }}\n />\n </span>\n);\n" }, "devhub.entity.addon.telegram.Viewer": { "": "const { handles } = props;\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 (!handles || handles.length === 0) {\n return (\n <CenteredMessage height={\"384px\"}>\n <h2>No Telegram Configured</h2>\n </CenteredMessage>\n );\n} else {\n return (\n <div>\n {(handles || []).map((tg) => {\n const pattern = /https:\\/\\/t.me\\/(.*)/;\n const includesHttp = tg.match(pattern);\n const handle = includesHttp ? includesHttp[1] : tg;\n return (\n <>\n <iframe\n iframeResizer\n src={\n \"https://j96g3uepe0.execute-api.us-east-1.amazonaws.com/groups-ui/\" +\n handle\n }\n frameborder=\"0\"\n // width and minWidth required by iframeResizer\n style={{\n width: \"1px\",\n minWidth: \"100%\",\n marginTop: \"20px\",\n }}\n ></iframe>\n\n <a href={includesHttp ? tg : \"https://t.me/\" + tg} target=\"_blank\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-primary\" },\n label: \"View More\",\n }}\n />\n </a>\n </>\n );\n })}\n </div>\n );\n}\n" }, "app": { "": "/**\n * This is the main entry point for the DevHub application.\n * Page route gets passed in through params, along with all other page props.\n */\n\nconst { onDraftStateChange } = VM.require(\n \"devhub.megha19.near/widget/devhub.entity.post.draft\"\n);\n\nconst { page, ...passProps } = props;\n\n// Import our modules\nconst { AppLayout } = VM.require(\n \"devhub.megha19.near/widget/devhub.components.templates.AppLayout\"\n);\n\nif (!AppLayout) {\n return <p>Loading modules...</p>;\n}\n\n// CSS styles to be used across the app.\n// Define fonts here, as well as any other global styles.\nconst Theme = styled.div`\n a {\n color: 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\nif (!page) {\n // If no page is specified, we default to the feed page TEMP\n page = \"home\";\n}\n\n// Track visits\n\nif (\"${REPL_POSTHOG_API_KEY}\".length === 47) {\n useEffect(() => {\n const hashedUserId = context.accountId\n ? Array.from(nacl.hash(Buffer.from(context.accountId)))\n .map((b) => (\"00\" + b.toString(16)).slice(-2))\n .join(\"\")\n : \"unauthenticated\";\n\n fetch(\"https://eu.posthog.com/capture/\", {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n },\n\n body: JSON.stringify({\n api_key: \"${REPL_POSTHOG_API_KEY}\",\n event: \"devhub_pageview\",\n properties: {\n distinct_id: hashedUserId,\n page,\n ...props,\n },\n timestamp: new Date().toISOString(),\n }),\n });\n }, [props]);\n}\n\n// This is our navigation, rendering the page based on the page parameter\nfunction Page() {\n const routes = page.split(\".\");\n switch (routes[0]) {\n case \"home\": {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.page.home\"\n props={passProps}\n />\n );\n }\n // ?page=communities\n case \"communities\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.communities\"}\n props={passProps}\n />\n );\n }\n case \"announcements\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.announcements\"}\n props={passProps}\n />\n );\n }\n\n // ?page=community\n case \"community\": {\n return (\n // Considering to consolidate this into a single widget,\n // where each level handles its own routing.\n // Modularizing a page just like we do with addons\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.community.Provider\"}\n props={{\n ...passProps,\n Children: (p) => {\n // passing props from the Provider into the Children\n switch (routes[1]) {\n // ?page=community.configuration\n case \"configuration\": {\n return (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.page.community.configuration\"\n }\n props={{\n ...passProps,\n ...p,\n }}\n />\n );\n }\n // ?page=community\n default: {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.community.index\"}\n props={{\n ...passProps,\n ...p,\n }}\n />\n );\n }\n }\n },\n }}\n />\n );\n }\n // ?page=feed\n case \"feed\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.feed\"}\n props={passProps}\n />\n );\n }\n // ?page=create\n case \"create\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.post.PostEditor\"}\n props={{ ...passProps, isCreatePostPage: true, onDraftStateChange }}\n />\n );\n }\n\n case \"create-proposal\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.Editor\"}\n props={{ ...passProps }}\n />\n );\n }\n\n case \"proposals\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.proposals\"}\n props={passProps}\n />\n );\n }\n case \"proposal\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.Proposal\"}\n props={passProps}\n />\n );\n }\n // ?page=about\n case \"about\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.about\"}\n props={passProps}\n />\n );\n }\n case \"contribute\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.contribute\"}\n props={passProps}\n />\n );\n }\n case \"profile\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.profile\"}\n props={passProps}\n />\n );\n }\n // ?page=blog\n case \"blog\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.blog\"}\n props={passProps}\n />\n );\n }\n case \"post\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.post\"}\n props={passProps}\n />\n );\n }\n case \"admin\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.admin.index\"}\n props={passProps}\n />\n );\n }\n default: {\n // TODO: 404 page\n return <p>404</p>;\n }\n }\n}\n\nreturn (\n <Theme>\n <AppLayout page={page}>\n <Page />\n </AppLayout>\n </Theme>\n);\n" }, "devhub.page.admin.homepageTab": { "": "const {\n getFeaturedCommunities,\n setFeaturedCommunities,\n getAllCommunitiesMetadata,\n} = VM.require(\"devhub.megha19.near/widget/core.adapter.devhub-contract\");\n\nconst { Tile } =\n VM.require(\"devhub.megha19.near/widget/devhub.components.molecule.Tile\") ||\n (() => <></>);\n\nif (\n !getFeaturedCommunities ||\n !setFeaturedCommunities ||\n !getAllCommunitiesMetadata ||\n !Tile\n) {\n return <p>Loading modules...</p>;\n}\n\nconst fc = getFeaturedCommunities();\n// The state will stay empty even after the data right data has been retrieved\nif (!fc) {\n return <p>Loading featured communities...</p>;\n}\nconst featuredCommunityList = fc || [];\n\nconst allCommunities = getAllCommunitiesMetadata();\n\nconst [communityMessage, setCommunityMessage] = useState(\"\");\nconst [previewConnect, setPreviewConnect] = useState(false);\n\nconst [communityHandles, setCommunityHandles] = useState(\n featuredCommunityList.map(({ handle }) => handle)\n);\nconst handleResetItems = () => {\n setCommunityHandles(featuredCommunityList.map(({ handle }) => handle));\n};\n\nfunction handleSubmit() {\n if (communityHandles.length < 4) {\n return setCommunityMessage(\"Can't set fewer than 4 communities\");\n }\n setFeaturedCommunities({ handles: communityHandles });\n}\n\nreturn (\n <>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.atom.Alert\"\n props={{\n onClose: () => setCommunityMessage(\"\"),\n message: communityMessage,\n }}\n />\n <Tile className=\"p-3 mb-3\">\n <h3> Manage featured communities</h3>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.ListEditor\"\n props={{\n data: {\n maxLength: 5,\n placeholder: \"Community handle\",\n prefix: \"Community handle\",\n list: communityHandles,\n },\n setList: setCommunityHandles,\n validate: (newItem) => {\n return allCommunities.map(({ handle }) => handle).includes(newItem);\n },\n invalidate: () =>\n setCommunityMessage(\n \"This community handle does not exist, make sure you use an existing handle.\"\n ),\n }}\n />\n\n <div\n className={\"d-flex align-items-center justify-content-end gap-3 mt-4\"}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: {\n root: \"btn-outline-danger shadow-none border-0\",\n },\n label: \"Cancel\",\n onClick: handleResetItems,\n }}\n />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn\" },\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Submit\",\n onClick: handleSubmit,\n }}\n />\n </div>\n </Tile>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.PostControls\"}\n props={{\n onClick: () => setPreviewConnect(!previewConnect),\n icon: previewConnect ? \"bi bi-toggle-on\" : \"bi bi-toggle-off\",\n title: \"Preview homepage\",\n testId: \"preview-homepage\",\n }}\n />\n <div class=\"mt-3\">\n {previewConnect && (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.island.connect\"\n props={{ ...props }}\n />\n )}\n </div>\n </>\n);\n" }, "devhub.page.blog": { "": "const { id } = props;\n\nconst { Page } =\n VM.require(\"devhub.megha19.near/widget/devhub.entity.addon.blog.Page\") ||\n (() => <></>);\n\nconst [showEditScreenData, setShowEditScreen] = useState(null);\n\nif (id && !showEditScreenData) {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.post.Postv2\"\n props={{\n postKey: id,\n template: (p) => (\n <Page\n {...(p || {})}\n onEdit={() => {\n setShowEditScreen({ ...p, data: { ...p.data, id: id } });\n }}\n accountId={context.accountId}\n />\n ),\n }}\n />\n );\n}\n\nconst HeaderContainer = styled.div`\n display: flex;\n width: 100%;\n height: 60px;\n padding: 1rem 3rem;\n align-items: center;\n flex-shrink: 0;\n background-color: #f4f4f4;\n\n @media screen and (max-width: 768px) {\n padding: 1rem;\n }\n`;\n\nconst Header = styled.h1`\n color: #555555;\n font-size: 24px;\n font-style: normal;\n font-weight: 500;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.24px;\n margin: 0;\n`;\n\nconst BlogContainer = styled.div`\n padding: 1rem 3rem;\n\n @media screen and (max-width: 768px) {\n padding: 1rem;\n }\n`;\n\nconst EditorContainer = styled.div`\n position: relative;\n width: 100%;\n padding: 20px;\n .cancel-icon {\n position: absolute;\n top: 30px;\n right: 30px;\n font-size: 25px;\n cursor: pointer;\n }\n`;\n\n// I like that this reduces duplicate code with the Viewer, but I don't like\n// that \"Latest Blog Posts\" carries over... // TOOD: create a common blog\n// feed... I think the addon.blog.Feed naming is confusing, as this should be a\n// generic feed component.\n\nif (showEditScreenData) {\n return (\n <EditorContainer>\n <div className=\"cancel-icon\" onClick={() => setShowEditScreen(null)}>\n <i class=\"bi bi-x-circle\"></i>\n </div>\n <Widget\n src={`devhub.megha19.near/widget/devhub.entity.addon.blog.Configurator`}\n props={{\n ...showEditScreenData,\n handle: showEditScreenData?.labels?.[1], // community-handle\n }}\n />\n </EditorContainer>\n );\n}\nreturn (\n <div className=\"w-100\">\n <Widget src={`devhub.megha19.near/widget/devhub.components.island.banner`} />\n <HeaderContainer>\n <Header>Blog</Header>\n </HeaderContainer>\n <BlogContainer>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.addon.blog.Viewer\"}\n props={{\n handle: \"developer-dao\",\n hideTitle: true,\n }}\n />\n </BlogContainer>\n </div>\n);\n" }, "devhub.entity.addon.blog.Feed": { "": "const { Item, Layout } = props;\n\nLayout = Layout || (() => <></>);\n\nconst Container = styled.div``;\n\nconst Loader = styled.div`\n text-align: center;\n padding: 20px;\n`;\n\nconst QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql/`;\nconst DISPLAY_COUNT = 10;\n\nconst fetchGraphQL = (operationsDoc, operationName, variables) => {\n return asyncFetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `bo_near` },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n};\n\nconst queryName =\n props.queryName ?? `bo_near_devhub_v38_posts_with_latest_snapshot`;\n\nconst query = `query DevhubPostsQuery($limit: Int = 100, $offset: Int = 0, $where: ${queryName}_bool_exp = {}) {\n ${queryName}(\n limit: $limit\n offset: $offset\n order_by: {ts: desc}\n where: $where\n ) {\n post_id\n }\n }\n`;\n\nconst [postIds, setPostIds] = useState([]);\nconst [loading, setLoading] = useState(false);\nconst [cachedItems, setCachedItems] = useState({});\nconst [hasNext, setHasNext] = useState(true);\n\nconst buildWhereClause = () => {\n let where = {};\n if (props.author) {\n where = { author_id: { _eq: props.author }, ...where };\n }\n if (props.term) {\n where = { description: { _ilike: `%${props.term}%` }, ...where };\n }\n if (props.includeLabels && Array.isArray(props.includeLabels)) {\n const labelConditions = props.includeLabels.map((label) => ({\n labels: { _contains: label },\n }));\n\n where = { _and: [...labelConditions, where] };\n }\n if (props.excludeLabels && Array.isArray(props.excludeLabels)) {\n const labelConditions = props.excludeLabels.map((label) => ({\n labels: { _nin: label },\n }));\n\n where = { _and: [...labelConditions, where] };\n }\n if (!props.recency) {\n where = { parent_id: { _is_null: true }, ...where };\n }\n return where;\n};\n\nconst fetchPostIds = (offset) => {\n if (!offset) {\n offset = 0;\n }\n if (loading) return;\n setLoading(true);\n const variables = { limit: DISPLAY_COUNT, offset, where: buildWhereClause() };\n fetchGraphQL(query, \"DevhubPostsQuery\", variables).then((result) => {\n if (result.status === 200) {\n if (result.body.data) {\n const data = result.body.data[queryName];\n const newPostIds = data.map((p) => p.post_id);\n setPostIds(offset === 0 ? newPostIds : [...postIds, ...newPostIds]);\n setHasNext(data.length >= variables.limit);\n } else {\n console.error(\"GraphQL Error:\", result.errors);\n }\n setLoading(false);\n }\n });\n};\n\nuseEffect(() => {\n fetchPostIds();\n}, [props.author, props.term, props.tag, props.recency]);\n\nconst handleLoadMore = () => {\n if (!hasNext) return;\n fetchPostIds(postIds.length);\n};\n\nconst renderLoader = () => <Loader>Loading...</Loader>;\n\nconst renderItem = (postId) => (\n <div key={postId}>\n {(props.renderItem && props.renderItem(postId)) || <div>Post {postId}</div>}\n </div>\n);\n\nconst cachedRenderItem = (postId) => {\n if (!(postId in cachedItems)) {\n cachedItems[postId] = renderItem(postId);\n setCachedItems({ ...cachedItems });\n }\n return cachedItems[postId];\n};\n\nreturn (\n <Container>\n {loading && renderLoader()}\n {postIds.length > 0 ? (\n <InfiniteScroll\n pageStart={0}\n dataLength={postIds.length}\n loadMore={handleLoadMore}\n hasMore={hasNext}\n loader={renderLoader()}\n >\n <Layout>\n {/* Layout */}\n {postIds.map(cachedRenderItem)}\n </Layout>\n </InfiniteScroll>\n ) : (\n <p class=\"text-secondary\">No posts</p>\n )}\n </Container>\n);\n" }, "devhub.entity.proposal.StatusTag": { "": "const timelineStatus = props.timelineStatus;\nconst size = props.size ?? \"md\";\n\nconst getClassNameByStatus = () => {\n switch (timelineStatus) {\n case \"DRAFT\":\n return \"grey\";\n case \"REVIEW\":\n return \"blue\";\n case \"APPROVED\":\n case \"APPROVED_CONDITIONALLY\":\n case \"FUNDED\":\n return \"green\";\n case \"PAYMENT_PROCESSING\":\n return \"orange\";\n case \"REJECTED\":\n case \"CANCELLED\":\n return \"warning\";\n default:\n return \"green\";\n }\n};\n\nconst Container = styled.div`\n font-size: ${({ size }) => {\n switch (size) {\n case \"sm\":\n return \"10px\";\n case \"lg\":\n return \"14px\";\n default:\n return \"12px\";\n }\n }};\n\n min-width: fit-content;\n\n .orange-tag {\n border: 1px solid #ff7a00 !important;\n color: #ff7a00 !important;\n }\n\n .warning-tag {\n border: 1px solid #f40303 !important;\n color: #f40303 !important;\n }\n\n .blue-tag {\n border: 1px solid #2c3e50 !important;\n color: #2c3e50 !important;\n }\n\n .grey-tag {\n border: 1px solid #979797 !important;\n color: #979797 !important;\n }\n\n .green-tag {\n border: 1px solid #04a46e !important;\n color: #04a46e !important;\n }\n\n .fw-bold {\n font-weight: 600 !important;\n }\n`;\n\nreturn (\n <Container size={size}>\n <div className={getClassNameByStatus() + \"-tag fw-bold rounded-2 p-1 px-2\"}>\n {(timelineStatus ?? \"\").replace(\"_\", \" \")}\n </div>\n </Container>\n);\n" }, "devhub.entity.addon.blog.editor.index": { "": "let theme = props.theme;\nlet variables = props.variables;\nconst editData = props.data;\n\nif (!variables) {\n variables = ``;\n}\n\nif (!theme) {\n theme = ``;\n}\n\nconst Root = styled.div`\n ${variables}\n ${theme}\n\n a {\n text-decoration: none;\n color: var(--base900);\n }\n`;\n\nconst Container = styled.div`\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n width: 100%;\n`;\n\n// What would be the first steps?\n// Create this container and an empty provider\n// Provider has some items, onChange, onSubmit console logs\n// Create a layout that takes in Editor\n\nreturn (\n <Root>\n {/* Get any layout */}\n <Container>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.addon.blog.editor.provider\"\n props={{\n handle: props.handle,\n Layout: (providerProps) => {\n const { data, onChange, onSubmit, onCancel, getData } =\n providerProps;\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.addon.blog.editor.layout\"\n props={{\n getData,\n editData: editData,\n Sidebar: (p) => (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.addon.blog.editor.sidebar\"\n props={{\n ...p,\n ...providerProps,\n }}\n />\n ),\n Content: (p) => (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.addon.blog.editor.content\"\n props={{\n onChange,\n onCancel,\n onSubmit,\n ...p,\n }}\n />\n ),\n }}\n />\n );\n },\n }}\n />\n </Container>\n </Root>\n);\n" }, "devhub.components.molecule.Button": { "": "const styles = `\n padding: 0.5rem 1.2rem !important;\n min-height: 36px;\n line-height: 1.5;\n text-decoration: none !important;\n\n &:not(.shadow-none) {\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n transition: box-shadow 0.6s;\n }\n\n &.btn-sm {\n padding: 0.5rem 0.8rem !important;\n min-height: 32px;\n line-height: 1;\n }\n\n &.btn-lg {\n padding: 1rem 1.5rem !important;\n min-height: 48px;\n }\n\n &.btn-primary {\n border: none;\n --bs-btn-color: #ffffff;\n --bs-btn-bg: #087990;\n --bs-btn-border-color: #087990;\n --bs-btn-hover-color: #ffffff;\n --bs-btn-hover-bg: #055160;\n --bs-btn-hover-border-color: #055160;\n --bs-btn-focus-shadow-rgb: 49, 132, 253;\n --bs-btn-active-color: #ffffff;\n --bs-btn-active-bg: #055160;\n --bs-btn-active-border-color: #055160;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #ffffff;\n --bs-btn-disabled-bg: #0551604a;\n }\n\n\t&.btn-outline-primary {\n\t\t--bs-btn-color: #087990;\n\t\t--bs-btn-border-color: #087990;\n\t\t--bs-btn-hover-color: #ffffff;\n\t\t--bs-btn-hover-bg: #087990;\n\t\t--bs-btn-hover-border-color: #087990;\n\t\t--bs-btn-focus-shadow-rgb: 49, 132, 253;\n\t\t--bs-btn-active-color: #ffffff;\n\t\t--bs-btn-active-bg: #087990;\n\t\t--bs-btn-active-border-color: #087990;\n\t\t--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n\t\t--bs-btn-disabled-border-color: #0551604a;\n\t}\n\n &[class*=\"btn-outline-\"] {\n border-width: 2px;\n }\n\n &.btn-outline-primary {\n --bs-btn-disabled-color: #6c757d8f;\n }\n\n &.btn-secondary {\n border: none;\n }\n\n &.btn-outline-secondary {\n --bs-btn-disabled-color: #6c757d8f;\n }\n\n &.btn-success {\n border: none;\n --bs-btn-disabled-bg: #35482a4a;\n }\n\n &.btn-outline-success {\n --bs-btn-disabled-color: #6c757d8f;\n }\n\n &.btn-danger {\n border: none;\n }\n\n &.btn-outline-danger {\n --bs-btn-disabled-color: #6c757d8f;\n }\n\n &.btn-warning {\n border: none;\n }\n\n &.btn-outline-warning {\n --bs-btn-disabled-color: #6c757d8f;\n }\n\n &.btn-info {\n border: none;\n }\n\n &.btn-outline-info {\n --bs-btn-disabled-color: #6c757d8f;\n }\n`;\n\nconst rootElementByType = (type) =>\n type === \"link\"\n ? styled.a`\n ${styles}\n `\n : styled.button`\n ${styles}\n `;\n\nconst Button = ({\n classNames,\n icon: iconProps,\n label,\n type,\n isHidden,\n notRounded,\n ...restProps\n}) => {\n const ButtonRoot = rootElementByType(type);\n\n return (\n <ButtonRoot\n className={[\n \"btn d-inline-flex align-items-center gap-2\",\n classNames?.root ?? \"btn-primary\",\n !notRounded ?? \"rounded-pill\",\n isHidden ?? false ? \"d-none\" : \"\",\n ].join(\" \")}\n style={{ width: \"fit-content\" }}\n {...restProps}\n >\n {iconProps !== null &&\n typeof iconProps === \"object\" &&\n !Array.isArray(iconProps) && (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.atom.Icon\"}\n props={iconProps}\n />\n )}\n <span className={classNames?.label} style={{ lineHeight: \"inherit\" }}>\n {label}\n </span>\n </ButtonRoot>\n );\n};\n\nreturn Button(props);\n" }, "devhub.components.organism.Feed": { "": "const { Feed } = VM.require(\"devs.near/widget/Feed\");\nFeed = Feed || (() => <></>);\nconst setPostExists = props.setPostExists ?? (() => {});\nconst showFlagAccountFeature = props.showFlagAccountFeature ?? false;\n\nconst filteredAccountIds = props.filteredAccountIds ?? [];\n\nconst GRAPHQL_ENDPOINT =\n props.GRAPHQL_ENDPOINT ?? \"https://near-queryapi.api.pagoda.co\";\n\nlet lastPostSocialApi = Social.index(\"post\", \"main\", {\n limit: 1,\n order: props.sort ? props.sort : \"desc\",\n});\n\nif (lastPostSocialApi == null) {\n return \"Loading...\";\n}\n\nState.init({\n // If QueryAPI Feed is lagging behind Social API, fallback to old widget.\n shouldFallback: false,\n});\n\nfunction fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(`${GRAPHQL_ENDPOINT}/v1/graphql`, {\n method: \"POST\",\n headers: { \"x-hasura-role\": \"dataplatform_near\" },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n}\n\nconst lastPostQuery = `\nquery IndexerQuery {\n dataplatform_near_social_feed_posts( limit: 1, order_by: { block_height: desc }) {\n block_height\n }\n}\n`;\n\nfetchGraphQL(lastPostQuery, \"IndexerQuery\", {})\n .then((feedIndexerResponse) => {\n if (\n feedIndexerResponse &&\n feedIndexerResponse.body.data.dataplatform_near_social_feed_posts.length >\n 0\n ) {\n const nearSocialBlockHeight = lastPostSocialApi[0].blockHeight;\n const feedIndexerBlockHeight =\n feedIndexerResponse.body.data.dataplatform_near_social_feed_posts[0]\n .block_height;\n\n const lag = nearSocialBlockHeight - feedIndexerBlockHeight;\n let shouldFallback = lag > 2 || !feedIndexerBlockHeight;\n if (shouldFallback === true) {\n console.log(\n \"Falling back to Social index feed. Block difference is: \",\n nearSocialBlockHeight - feedIndexerBlockHeight\n );\n State.update({ shouldFallback });\n }\n } else {\n console.log(\n \"Falling back to Social index feed. No QueryApi data received.\"\n );\n State.update({ shouldFallback: true });\n }\n })\n .catch((error) => {\n console.log(\n \"Error while fetching QueryApi feed (falling back to index feed): \",\n error\n );\n State.update({ shouldFallback: true });\n });\n\nreturn (\n <div>\n {state.shouldFallback ? (\n <Feed\n index={[\n {\n action: props.action ? props.action : \"post\",\n key: \"main\",\n options: {\n limit: 10,\n subscribe: props.onNewUnseenPosts ? true : false,\n order: props.sort ? props.sort : \"desc\",\n accountId: filteredAccountIds,\n },\n cacheOptions: {\n ignoreCache: true,\n },\n },\n ]}\n Item={(item) => {\n setPostExists(true);\n\n return (\n <Widget\n src=\"near/widget/v1.Posts.Post\"\n loading={<div className=\"w-100\" style={{ height: \"200px\" }} />}\n props={{\n accountId: item.accountId,\n blockHeight: item.blockHeight,\n filteredAccountIds: filteredAccountIds,\n }}\n />\n );\n }}\n />\n ) : (\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.organism.Feed.NearQueryApi`}\n props={{\n GRAPHQL_ENDPOINT,\n filteredAccountIds: filteredAccountIds,\n showFlagAccountFeature: showFlagAccountFeature,\n onNewUnseenPosts: props.onNewUnseenPosts,\n setPostExists: setPostExists,\n sort: props.sort,\n }}\n />\n )}\n </div>\n);\n" }, "devhub.components.molecule.AccountAutocomplete": { "": "if (!context.accountId || !props.term) return <></>;\n\nlet results = [];\nconst filterAccounts = props.filterAccounts ?? []; // hide certain accounts from the list\nconst profilesData = Social.get(\"*/profile/name\", \"final\") || {};\nconst followingData = Social.get(\n `${context.accountId}/graph/follow/**`,\n \"final\"\n);\nif (!profilesData) return <></>;\nconst profiles = Object.entries(profilesData);\nconst term = (props.term || \"\").replace(/\\W/g, \"\").toLowerCase();\nconst limit = 5;\n\nfor (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\nresults.sort((a, b) => b.score - a.score);\nresults = results.slice(0, limit);\nif (filterAccounts?.length > 0) {\n results = results.filter((item) => !filterAccounts?.includes(item.accountId));\n}\n\nfunction onResultClick(id) {\n props.onSelect && props.onSelect(id);\n}\n\nconst Wrapper = styled.div`\n position: relative;\n background: #eceef0;\n\n &::before {\n content: \"\";\n display: block;\n position: absolute;\n right: 0;\n width: 6px;\n height: 100%;\n background: linear-gradient(\n to left,\n rgba(236, 238, 240, 1),\n rgba(236, 238, 240, 0)\n );\n z-index: 10;\n }\n`;\n\nconst Scroller = styled.div`\n position: relative;\n display: flex;\n padding: 6px;\n gap: 6px;\n overflow: auto;\n scroll-behavior: smooth;\n align-items: center;\n scrollbar-width: none;\n -ms-overflow-style: none;\n &::-webkit-scrollbar {\n display: none;\n }\n\n > * {\n max-width: 175px;\n flex-grow: 0;\n flex-shrink: 0;\n\n button {\n border: 1px solid #eceef0;\n background: #fff !important;\n border-radius: 6px;\n padding: 3px 6px;\n transition: all 200ms;\n\n &:focus,\n &:hover {\n border-color: #687076;\n }\n }\n }\n`;\n\nconst CloseButton = styled.button`\n background: none;\n border: none;\n display: block;\n padding: 12px;\n color #687076;\n transition: all 200ms;\n\n &:hover {\n color: #000;\n }\n`;\n\nif (results.length === 0) return <></>;\n\nreturn (\n <Wrapper>\n <Scroller>\n <CloseButton tabIndex={-1} type=\"button\" onClick={props.onClose}>\n <i className=\"bi bi-x-circle\" />\n </CloseButton>\n\n {results.map((result) => {\n return (\n <Widget\n key={result.accountId}\n src=\"near/widget/AccountProfile\"\n props={{\n avatarSize: \"34px\",\n accountId: result.accountId,\n onClick: onResultClick,\n overlayPlacement: \"bottom\",\n }}\n />\n );\n })}\n </Scroller>\n </Wrapper>\n);\n" }, "devhub.components.feed.SubscribedFeed": { "": "const indexKey = props.indexKey ?? \"main\";\nconst groupId = props.groupId;\nconst permissions = props.permissions;\n\nconst index = [\n {\n action: \"post\",\n key: indexKey,\n options: {\n limit: 10,\n order: props.sort ? props.sort : \"desc\",\n subscribe: true,\n accountId: props.accounts,\n },\n cacheOptions: {\n ignoreCache: true,\n },\n },\n {\n action: \"repost\",\n key: indexKey,\n options: {\n limit: 10,\n order: props.sort ? props.sort : \"desc\",\n subscribe: true,\n accountId: props.accounts,\n },\n cacheOptions: {\n ignoreCache: true,\n },\n },\n];\n\nconst isPremiumFeed = props.isPremiumFeed;\nconst commentAccounts = props.commentAccounts;\nconst renderedPosts = {};\n\nconst makePostItem = (a) => ({\n type: \"social\",\n path: `${a.accountId}/post/main`,\n blockHeight: a.blockHeight,\n});\n\nconst renderPost = (a) => {\n if (a.value.type !== \"md\") {\n return false;\n }\n const item = JSON.stringify(makePostItem(a));\n if (item in renderedPosts) {\n return false;\n }\n renderedPosts[item] = true;\n\n return (\n <div key={JSON.stringify(a)} style={{ minHeight: \"150px\" }}>\n <Widget\n loading={<div className=\"w-100\" style={{ height: \"200px\" }} />}\n src=\"mob.near/widget/MainPage.N.Post\"\n props={{\n accountId: a.accountId,\n blockHeight: a.blockHeight,\n isPremiumFeed,\n commentAccounts,\n indexKey,\n groupId,\n permissions,\n }}\n />\n </div>\n );\n};\n\nconst repostSvg = (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n fill=\"currentColor\"\n viewBox=\"0 2 24 24\"\n stroke=\"currentColor\"\n strokeWidth=\"1\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M4.854 1.146a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L4 2.707V12.5A2.5 2.5 0 0 0 6.5 15h8a.5.5 0 0 0 0-1h-8A1.5 1.5 0 0 1 5 12.5V2.707l3.146 3.147a.5.5 0 1 0 .708-.708l-4-4z\"\n transform=\"rotate(180, 12, 12), translate(0, 4)\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M4.854 1.146a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L4 2.707V12.5A2.5 2.5 0 0 0 6.5 15h8a.5.5 0 0 0 0-1h-8A1.5 1.5 0 0 1 5 12.5V2.707l3.146 3.147a.5.5 0 1 0 .708-.708l-4-4z\"\n transform=\"translate(0, 4)\"\n />\n </svg>\n);\n\nconst extractParentPost = (item) => {\n if (!item || item.type !== \"social\" || !item.path || !item.blockHeight) {\n return undefined;\n }\n const accountId = item.path.split(\"/\")[0];\n return `${accountId}/post/main` === item.path\n ? { accountId, blockHeight: item.blockHeight }\n : undefined;\n};\n\nconst renderRepost = (a) => {\n if (a.value.type !== \"repost\") {\n return false;\n }\n const post = extractParentPost(a.value.item);\n if (!post) {\n return false;\n }\n const item = JSON.stringify(makePostItem(post));\n if (item in renderedPosts) {\n return false;\n }\n renderedPosts[item] = true;\n\n return (\n <div key={JSON.stringify(a)} style={{ minHeight: \"150px\" }}>\n <div\n className=\"text-muted\"\n style={{\n fontSize: \"13px\",\n fontWeight: 700,\n marginLeft: \"24px\",\n marginBottom: \"-24px\",\n paddingTop: \"4px\",\n position: \"relative\",\n zIndex: 1,\n }}\n >\n {repostSvg}{\" \"}\n <span style={{ marginLeft: \"8px\" }} data-testid=\"repost\">\n Reposted by{\" \"}\n <Widget\n loading={a.accountId}\n src=\"mob.near/widget/N.ProfileLine\"\n props={{\n accountId: a.accountId,\n hideImage: true,\n hideAccountId: true,\n tooltip: true,\n }}\n />\n </span>\n </div>\n <Widget\n loading={<div className=\"w-100\" style={{ height: \"200px\" }} />}\n src=\"mob.near/widget/MainPage.N.Post\"\n props={{\n accountId: post.accountId,\n blockHeight: post.blockHeight,\n reposted: true,\n isPremiumFeed,\n commentAccounts,\n indexKey,\n groupId,\n permissions,\n }}\n />\n </div>\n );\n};\n\nconst renderItem = (item) =>\n item.action === \"post\" ? renderPost(item) : renderRepost(item);\nreturn (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.feed.MergedIndexFeed\"\n props={{\n index,\n renderItem,\n filter: props.filter,\n threshold: props.threshold,\n }}\n />\n);\n" }, "devhub.components.atom.Alert": { "": "const Alert = ({ onClose, message }) =>\n message && (\n <div class=\"alert alert-warning alert-dismissible fade show\" role=\"alert\">\n {message}\n <button\n type=\"button\"\n class=\"btn-close\"\n data-bs-dismiss=\"alert\"\n aria-label=\"Close\"\n onClick={onClose}\n ></button>\n </div>\n );\n\nreturn Alert(props);\n" }, "devhub.components.island.banner": { "": "const imageLink =\n \"https://ipfs.near.social/ipfs/bafybeiap2mzwsly4apaldxguiunx4rjwqyadksj5yxuzwrww3kue3ao5qe\";\n\nconst HeroSection = styled.div`\n position: relative;\n height: auto;\n z-index: 3;\n width: 70%;\n background: #00ec97;\n clip-path: polygon(0 0, 100% 0%, 75% 100%, 0% 100%);\n\n padding-top: 2rem;\n padding-bottom: 2rem;\n padding-left: 3.375rem;\n\n @media screen and (max-width: 768px) {\n width: 100%;\n padding: 1rem 1.5rem;\n clip-path: none;\n }\n`;\n\nconst Title = styled.h1`\n color: #f4f4f4;\n font-size: 4rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 88px */\n letter-spacing: -1.76px;\n\n @media screen and (max-width: 768px) {\n font-size: 2.25rem;\n letter-spacing: -0.72px;\n margin: 0;\n }\n`;\n\nconst Container = styled.div`\n position: relative;\n width: 100%;\n height: max-content;\n overflow: hidden;\n\n @media screen and (max-width: 768px) {\n background: #f4f4f4;\n }\n`;\n\nconst ImageContainer = styled.div`\n width: 100%;\n height: 100%;\n position: absolute;\n top: 0;\n right: 0;\n z-index: 1;\n background: transparent;\n\n @media screen and (max-width: 768px) {\n display: none;\n }\n`;\n\nconst Image = styled.img`\n margin-left: 15.625rem;\n height: 100%;\n width: 100%;\n filter: grayscale(100%);\n object-fit: cover;\n`;\n\nconst MobileImage = styled.img`\n display: none;\n\n width: 100%;\n height: 196px;\n\n width: 100%;\n object-fit: cover;\n filter: grayscale(1);\n\n @media screen and (max-width: 768px) {\n display: block;\n }\n`;\n\nreturn (\n <Container>\n <HeroSection>\n <Title>\n The decentralized <br />\n <span style={{ color: \"#101820\" }}>home base</span> <br />\n for NEAR builders\n </Title>\n </HeroSection>\n <MobileImage src={imageLink} />\n <ImageContainer>\n <Image src={imageLink} />\n </ImageContainer>\n </Container>\n);\n" }, "devhub.components.molecule.NavbarDropdown": { "": "const title = props.title;\nconst links = props.links;\nconst href = props.href;\n\nconst [showMenu, setShowMenu] = useState(false);\n\nconst { href: linkHref } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nlinkHref || (linkHref = () => {});\n\nconst Dropdown = styled.div`\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: center;\n\n p {\n &.active {\n color: #fff;\n\n &:hover {\n text-decoration: none;\n color: #096d50 !important;\n }\n }\n }\n`;\n\nconst DropdownMenu = styled.div`\n z-index: 50;\n position: absolute;\n top: 2.25rem;\n\n &.active {\n padding: 0.5rem 1rem;\n padding-top: 1rem;\n border-radius: 1rem;\n background: rgba(217, 217, 217, 0.7);\n backdrop-filter: blur(5px);\n width: max-content;\n animation: slide-down 300ms ease;\n transform-origin: top center;\n }\n\n @keyframes slide-down {\n 0% {\n transform: scaleY(0);\n }\n 100% {\n transform: scaleY(1);\n }\n }\n`;\n\nconst DropdownLink = styled.div`\n color: inherit;\n text-decoration: none;\n\n &.active {\n color: #555555;\n }\n\n &:hover {\n text-decoration: none;\n color: #096d50 !important;\n }\n`;\n\nreturn (\n <Dropdown\n onMouseEnter={() => setShowMenu(true)}\n onMouseLeave={() => setShowMenu(false)}\n >\n {href ? (\n <DropdownLink className={href === props.page && \"active\"} href={href}>\n <Link\n style={{ textDecoration: \"none\" }}\n to={linkHref({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: href },\n })}\n >\n {title}\n </Link>\n </DropdownLink>\n ) : (\n <p className={`m-0 py-2 nav-dropdown`} style={{ cursor: \"default\" }}>\n {title} ↓\n </p>\n )}\n {showMenu && links.length !== 0 && (\n <DropdownMenu className={`${showMenu && \"active\"}`}>\n <div className=\"d-flex flex-column gap-3\">\n {links.map((link) => (\n // Check if the link is external\n <DropdownLink\n className={link.href === props.page && \"active\"}\n key={`${link.title}-${link.href}`}\n >\n {link.href.startsWith(\"http://\") ||\n link.href.startsWith(\"https://\") ? (\n // External link: Render an <a> tag\n <a\n href={link.href}\n style={{ textDecoration: \"none\" }}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {link.title}\n </a>\n ) : (\n // Internal link: Render the <Link> component\n <Link\n style={{ textDecoration: \"none\" }}\n to={linkHref({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: link.href },\n })}\n >\n {link.title}\n </Link>\n )}\n </DropdownLink>\n ))}\n </div>\n </DropdownMenu>\n )}\n </Dropdown>\n);\n" }, "devhub.components.molecule.SimpleMDE": { "": "/**\n * iframe embedding a SimpleMDE component\n * https://github.com/sparksuite/simplemde-markdown-editor\n */\n\nconst data = props.data;\nconst onChange = props.onChange ?? (() => {});\nconst height = props.height ?? \"390\";\nconst className = props.className ?? \"w-100\";\nconst embeddCSS = props.embeddCSS;\n\nState.init({\n iframeHeight: height,\n message: props.data,\n});\n\nconst profilesData = Social.get(\"*/profile/name\", \"final\");\nconst followingData = Social.get(\n `${context.accountId}/graph/follow/**`,\n \"final\"\n);\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;\n\nconst queryName =\n \"thomasguntenaar_near_devhub_proposals_quebec_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 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 ${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 query = '';\nlet showAccountAutoComplete = ${showAccountAutoComplete};\nlet showProposalIdAutoComplete = ${showProposalIdAutoComplete};\n\nfunction getSuggestedAccounts(term) {\n let results = [];\n\n term = (term || \"\").replace(/\\W/g, \"\").toLowerCase();\n const limit = 5;\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\"] = { name: { _ilike: \"%\" + id + \"%\" } };\n }\n }\n await asyncFetch(\"https://near-queryapi.api.pagoda.co/v1/graphql\", {\n method: \"POST\",\n headers: { \"x-hasura-role\": \"thomasguntenaar_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 \"thomasguntenaar_near_devhub_proposals_quebec_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 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});\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 updateIframeHeight();\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 = \"https://near.social/devhub.near/widget/app?page=proposal&id=\" + 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 === 'autocompleteSelected') {\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});\n</script>\n</body>\n</html>\n`;\n\nreturn (\n <iframe\n className={className}\n style={{\n height: `${state.iframeHeight}px`,\n }}\n srcDoc={code}\n message={{\n content: props.data?.content ?? \"\",\n followingData,\n profilesData: JSON.stringify(profilesData),\n query: query,\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 }\n }}\n />\n);\n" }, "devhub.entity.proposal.Editor": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\nconst draftKey = \"PROPOSAL_EDIT\";\nhref || (href = () => {});\n\nconst { id, timestamp } = props;\n\nconst isEditPage = typeof id === \"string\";\nconst author = context.accountId;\nconst FundingDocs =\n \"https://near.social/devhub.near/widget/app?page=community&handle=developer-dao&tab=funding\";\n\nif (!author) {\n return (\n <Widget src={\"devhub.megha19.near/widget/devhub.entity.proposal.LoginScreen\"} />\n );\n}\nlet editProposalData = null;\nlet draftProposalData = null;\n\nif (isEditPage) {\n editProposalData = Near.view(\"devhub.near\", \"get_proposal\", {\n proposal_id: parseInt(id),\n });\n}\n\nconst Container = styled.div`\n input {\n font-size: 14px !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 textarea {\n font-size: 14px !important;\n }\n\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 .text-sm {\n font-size: 13px;\n }\n\n @media screen and (max-width: 768px) {\n .h6 {\n font-size: 14px !important;\n }\n\n .h5 {\n font-size: 16px !important;\n }\n\n .text-sm {\n font-size: 11px;\n }\n\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n\n .border-bottom {\n border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color);\n }\n\n .text-xs {\n font-size: 10px;\n }\n\n .flex-2 {\n flex: 2;\n }\n\n .flex-1 {\n flex: 1;\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 &:hover {\n background-color: #f4f4f4;\n }\n }\n\n .border-1 {\n border: 1px solid #e2e6ec;\n }\n .green-btn {\n background-color: #04a46e !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n\n .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\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 .input-icon {\n display: flex;\n height: 100%;\n align-items: center;\n border-right: 1px solid #dee2e6;\n padding-right: 10px;\n }\n\n /* Tooltip container */\n .custom-tooltip {\n position: relative;\n display: inline-block;\n }\n\n /* Tooltip text */\n .custom-tooltip .tooltiptext {\n visibility: hidden;\n width: 250px;\n background-color: #fff;\n color: #6c757d;\n text-align: center;\n padding: 10px;\n border-radius: 6px;\n font-size: 12px;\n border: 0.2px solid #6c757d;\n\n /* Position the tooltip text */\n position: absolute;\n z-index: 1;\n bottom: 125%;\n left: -30px;\n\n /* Fade in tooltip */\n opacity: 0;\n transition: opacity 0.3s;\n }\n\n /* Tooltip arrow */\n .custom-tooltip .tooltiptext::after {\n content: \"\";\n position: absolute;\n top: 100%;\n left: 15%;\n margin-left: -5px;\n border-width: 5px;\n border-style: solid;\n border-color: #555 transparent transparent transparent;\n }\n\n /* Show the tooltip text when you mouse over the tooltip container */\n .custom-tooltip:hover .tooltiptext {\n visibility: visible;\n opacity: 1;\n }\n\n .form-check-input:checked {\n background-color: #04a46e !important;\n border-color: #04a46e !important;\n }\n\n .gap-6 {\n gap: 2.5rem;\n }\n\n a.no-space {\n display: inline-block;\n }\n`;\n\nconst Heading = styled.div`\n font-size: 24px;\n font-weight: 700;\n\n @media screen and (max-width: 768px) {\n font-size: 18px;\n }\n`;\n\nconst tokensOptions = [\n { label: \"NEAR\", value: \"NEAR\" },\n { label: \"USDT\", value: \"USDT\" },\n {\n label: \"USDC\",\n value: \"USDC\",\n },\n {\n label: \"Other\",\n value: \"OTHER\",\n },\n];\n\nconst devdaoAccount = \"neardevdao.near\";\n\nconst [category, setCategory] = useState(null);\nconst [title, setTitle] = useState(null);\nconst [description, setDescription] = useState(null);\nconst [summary, setSummary] = useState(null);\nconst [consent, setConsent] = useState({ toc: false, coc: false });\nconst [linkedProposals, setLinkedProposals] = useState([]);\nconst [receiverAccount, setReceiverAccount] = useState(context.accountId);\nconst [requestedSponsor, setRequestedSponsor] = useState(devdaoAccount);\nconst [requestedSponsorshipAmount, setRequestedSponsorshipAmount] =\n useState(null);\nconst [requestedSponsorshipToken, setRequestedSponsorshipToken] = useState(\n tokensOptions[2]\n);\nconst [supervisor, setSupervisor] = useState(null);\nconst [allowDraft, setAllowDraft] = useState(true);\n\nconst [loading, setLoading] = useState(true);\nconst [disabledSubmitBtn, setDisabledSubmitBtn] = useState(false);\nconst [isDraftBtnOpen, setDraftBtnOpen] = useState(false);\nconst [selectedStatus, setSelectedStatus] = useState(\"draft\");\nconst [isReviewModalOpen, setReviewModal] = useState(false);\nconst [isCancelModalOpen, setCancelModal] = useState(false);\n\nconst [showProposalPage, setShowProposalPage] = useState(false); // when user creates/edit a proposal and confirm the txn, this is true\nconst [proposalId, setProposalId] = useState(null);\nconst [proposalIdsArray, setProposalIdsArray] = useState(null);\nconst [isTxnCreated, setCreateTxn] = useState(false);\nconst [oldProposalData, setOldProposalData] = useState(null);\n\nif (allowDraft) {\n draftProposalData = Storage.privateGet(draftKey);\n}\n\nconst memoizedDraftData = useMemo(\n () => ({\n id: editProposalData.id ?? null,\n snapshot: {\n name: title,\n description: description,\n category: category,\n summary: summary,\n requested_sponsorship_usd_amount: requestedSponsorshipAmount,\n requested_sponsorship_paid_in_currency: requestedSponsorshipToken.value,\n receiver_account: receiverAccount,\n supervisor: supervisor,\n requested_sponsor: requestedSponsor,\n },\n }),\n [\n title,\n summary,\n description,\n category,\n requestedSponsorshipAmount,\n requestedSponsorshipToken,\n receiverAccount,\n supervisor,\n requestedSponsor,\n ]\n);\n\nuseEffect(() => {\n if (allowDraft) {\n let data = editProposalData || JSON.parse(draftProposalData);\n let snapshot = data.snapshot;\n if (data) {\n if (timestamp) {\n snapshot =\n data.snapshot_history.find((item) => item.timestamp === timestamp) ??\n data.snapshot;\n }\n if (\n draftProposalData &&\n editProposalData &&\n editProposalData.id === JSON.parse(draftProposalData).id\n ) {\n snapshot = {\n ...editProposalData.snapshot,\n ...JSON.parse(draftProposalData).snapshot,\n };\n }\n setCategory(snapshot.category);\n setTitle(snapshot.name);\n setSummary(snapshot.summary);\n setDescription(snapshot.description);\n setReceiverAccount(snapshot.receiver_account);\n setRequestedSponsor(snapshot.requested_sponsor);\n setRequestedSponsorshipAmount(snapshot.requested_sponsorship_usd_amount);\n setSupervisor(snapshot.supervisor);\n\n const token = tokensOptions.find(\n (item) => item.value === snapshot.requested_sponsorship_paid_in_currency\n );\n setRequestedSponsorshipToken(token ?? tokensOptions[2]);\n if (isEditPage) {\n setConsent({ toc: true, coc: true });\n }\n }\n setLoading(false);\n }\n}, [editProposalData, draftProposalData, allowDraft]);\n\nuseEffect(() => {\n if (draftProposalData) {\n setAllowDraft(false);\n }\n}, [draftProposalData]);\n\nuseEffect(() => {\n if (showProposalPage) {\n return;\n }\n setDisabledSubmitBtn(\n isTxnCreated ||\n !title ||\n !description ||\n !summary ||\n !category ||\n !requestedSponsorshipAmount ||\n !receiverAccount ||\n !requestedSponsor ||\n !consent.toc ||\n !consent.coc\n );\n const handler = setTimeout(() => {\n Storage.privateSet(draftKey, JSON.stringify(memoizedDraftData));\n }, 10000);\n\n return () => clearTimeout(handler);\n}, [\n memoizedDraftData,\n draftKey,\n draftProposalData,\n consent,\n isTxnCreated,\n showProposalPage,\n]);\n\nuseEffect(() => {\n if (\n editProposalData &&\n editProposalData?.snapshot?.linked_proposals?.length > 0\n ) {\n editProposalData.snapshot.linked_proposals.map((item) => {\n useCache(\n () =>\n Near.asyncView(\"devhub.near\", \"get_proposal\", {\n proposal_id: parseInt(item),\n }).then((proposal) => {\n setLinkedProposals([\n ...linkedProposals,\n {\n label: \"# \" + proposal.id + \" : \" + proposal.snapshot.name,\n value: proposal.id,\n },\n ]);\n }),\n item + \"linked_proposals\",\n { subscribe: false }\n );\n });\n }\n}, [editProposalData]);\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\n// show proposal created after txn approval for popup wallet\nuseEffect(() => {\n if (isTxnCreated) {\n if (editProposalData) {\n setOldProposalData(editProposalData);\n if (\n editProposalData &&\n typeof editProposalData === \"object\" &&\n oldProposalData &&\n typeof oldProposalData === \"object\" &&\n JSON.stringify(editProposalData) !== JSON.stringify(oldProposalData)\n ) {\n setCreateTxn(false);\n setProposalId(editProposalData.id);\n setShowProposalPage(true);\n }\n } else {\n const proposalIds = Near.view(\n \"devhub.near\",\n \"get_all_proposal_ids\"\n );\n if (Array.isArray(proposalIds) && !proposalIdsArray) {\n setProposalIdsArray(proposalIds);\n }\n if (\n Array.isArray(proposalIds) &&\n Array.isArray(proposalIdsArray) &&\n proposalIds.length !== proposalIdsArray.length\n ) {\n setCreateTxn(false);\n setProposalId(proposalIds[proposalIds.length - 1]);\n setShowProposalPage(true);\n }\n }\n }\n});\n\nuseEffect(() => {\n if (props.transactionHashes) {\n setLoading(true);\n useCache(\n () =>\n asyncFetch(\"https://rpc.mainnet.near.org\", {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: \"dontcare\",\n method: \"tx\",\n params: [props.transactionHashes, context.accountId],\n }),\n }).then((transaction) => {\n const transaction_method_name =\n transaction?.body?.result?.transaction?.actions[0].FunctionCall\n .method_name;\n\n const is_edit_or_add_post_transaction =\n transaction_method_name == \"add_proposal\" ||\n transaction_method_name == \"edit_proposal\";\n\n if (is_edit_or_add_post_transaction) {\n setShowProposalPage(true);\n Storage.privateSet(draftKey, null);\n }\n // show the latest created proposal to user\n if (transaction_method_name == \"add_proposal\") {\n useCache(\n () =>\n Near.asyncView(\n \"devhub.near\",\n \"get_all_proposal_ids\"\n ).then((proposalIdsArray) => {\n setProposalId(\n proposalIdsArray?.[proposalIdsArray?.length - 1]\n );\n }),\n props.transactionHashes + \"proposalIds\",\n { subscribe: false }\n );\n } else {\n setProposalId(id);\n }\n setLoading(false);\n }),\n props.transactionHashes + context.accountId,\n { subscribe: false }\n );\n } else {\n if (showProposalPage) {\n setShowProposalPage(false);\n }\n }\n}, [props.transactionHashes]);\n\nconst DropdowntBtnContainer = styled.div`\n font-size: 13px;\n min-width: 150px;\n\n .custom-select {\n position: relative;\n }\n\n .select-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n border: 1px solid #ccc;\n border-radius-top: 5px;\n cursor: pointer;\n background-color: #fff;\n border-radius: 5px;\n }\n\n .no-border {\n border: none !important;\n }\n\n .options-card {\n position: absolute;\n top: 100%;\n left: 0;\n width: 200%;\n border: 1px solid #ccc;\n background-color: #fff;\n padding: 0.5rem;\n z-index: 99;\n font-size: 13px;\n border-radius:0.375rem !important;\n }\n\n .left {\n right: 0 !important;\n left: auto !important;\n }\n\n @media screen and (max-width: 768px) {\n .options-card {\n right: 0 !important;\n left: auto !important;\n }\n }\n\n .option {\n margin-block: 5px;\n padding: 10px;\n cursor: pointer;\n border-bottom: 1px solid #f0f0f0;\n transition: background-color 0.3s ease;\n border-radius: 0.375rem !important;\n }\n\n .option:hover {\n background-color: #f0f0f0; /* Custom hover effect color */\n }\n\n .option:last-child {\n border-bottom: none;\n }\n\n .selected {\n background-color: #f0f0f0;\n }\n\n .disabled {\n background-color: #f4f4f4 !important;\n cursor: not-allowed !important;\n font-weight: 500;\n color: #b3b3b3;\n }\n\n .disabled .circle {\n opacity: 0.5;\n }\n\n .circle {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n }\n\n .grey {\n background-color: #818181;\n }\n\n .green {\n background-color: #04a46e;\n }\n\n a:hover {\n text-decoration: none;\n }\n\n}\n`;\n\nconst LoadingButtonSpinner = (\n <span\n class=\"submit-proposal-draft-loading-indicator spinner-border spinner-border-sm\"\n role=\"status\"\n aria-hidden=\"true\"\n ></span>\n);\n\nconst SubmitBtn = () => {\n const btnOptions = [\n {\n iconColor: \"grey\",\n label: \"Submit Draft\",\n description:\n \"The author can still edit the proposal and build consensus before sharing it with sponsors.\",\n value: \"draft\",\n },\n {\n iconColor: \"green\",\n label: \"Ready for Review\",\n description:\n \"Start the official review process with sponsors. This will lock the editing function, but comments are still open.\",\n value: \"review\",\n },\n ];\n\n const handleOptionClick = (option) => {\n setDraftBtnOpen(false);\n setSelectedStatus(option.value);\n };\n\n const toggleDropdown = () => {\n setDraftBtnOpen(!isDraftBtnOpen);\n };\n\n const handleSubmit = () => {\n const isDraft = selectedStatus === \"draft\";\n if (isDraft) {\n onSubmit({ isDraft });\n cleanDraft();\n } else {\n setReviewModal(true);\n }\n };\n\n const selectedOption = btnOptions.find((i) => i.value === selectedStatus);\n\n return (\n <DropdowntBtnContainer>\n <div\n className=\"custom-select\"\n tabIndex=\"0\"\n onBlur={() => setDraftBtnOpen(false)}\n >\n <div\n className={\n \"select-header d-flex gap-1 align-items-center submit-draft-button \" +\n (disabledSubmitBtn && \"disabled\")\n }\n >\n <div\n onClick={() => !disabledSubmitBtn && handleSubmit()}\n className=\"p-2 d-flex gap-2 align-items-center \"\n >\n {isTxnCreated ? (\n LoadingButtonSpinner\n ) : (\n <div className={\"circle \" + selectedOption.iconColor}></div>\n )}\n <div className={`selected-option`}>{selectedOption.label}</div>\n </div>\n <div\n className=\"h-100 p-2\"\n style={{ borderLeft: \"1px solid #ccc\" }}\n onClick={!disabledSubmitBtn && toggleDropdown}\n >\n <i class={`bi bi-chevron-${isDraftBtnOpen ? \"up\" : \"down\"}`}></i>\n </div>\n </div>\n\n {isDraftBtnOpen && (\n <div className=\"options-card\">\n {btnOptions.map((option) => (\n <div\n key={option.value}\n className={`option ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n onClick={() => handleOptionClick(option)}\n >\n <div className={`d-flex gap-2 align-items-center`}>\n <div className={\"circle \" + option.iconColor}></div>\n <div className=\"fw-bold\">{option.label}</div>\n </div>\n <div className=\"text-muted text-xs\">{option.description}</div>\n </div>\n ))}\n </div>\n )}\n </div>\n </DropdowntBtnContainer>\n );\n};\n\nconst onSubmit = ({ isDraft, isCancel }) => {\n setCreateTxn(true);\n console.log(\"submitting transaction\");\n const linkedProposalsIds = linkedProposals.map((item) => item.value) ?? [];\n const body = {\n proposal_body_version: \"V0\",\n name: title,\n description: description,\n category: category,\n summary: summary,\n linked_proposals: linkedProposalsIds,\n requested_sponsorship_usd_amount: requestedSponsorshipAmount,\n requested_sponsorship_paid_in_currency: requestedSponsorshipToken.value,\n receiver_account: receiverAccount,\n supervisor: supervisor || null,\n requested_sponsor: requestedSponsor,\n timeline: isCancel\n ? {\n status: \"CANCELLED\",\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n }\n : isDraft\n ? { status: \"DRAFT\" }\n : {\n status: \"REVIEW\",\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n },\n };\n const args = { labels: [], body: body };\n if (isEditPage) {\n args[\"id\"] = editProposalData.id;\n }\n\n Near.call([\n {\n contractName: \"devhub.near\",\n methodName: isEditPage ? \"edit_proposal\" : \"add_proposal\",\n args: args,\n gas: 270000000000000,\n },\n ]);\n};\n\nfunction cleanDraft() {\n Storage.privateSet(draftKey, null);\n}\n\nif (loading) {\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.megha19.near/widget/devhub.components.molecule.Spinner\"}\n />\n </div>\n );\n}\n\nconst [collapseState, setCollapseState] = useState({});\n\nconst CollapsibleContainer = ({ title, children, noPaddingTop }) => {\n return (\n <div\n className={\n \"border-bottom py-4 \" +\n (noPaddingTop && \"pt-0 \") +\n (collapseState[title] && \" pb-0\")\n }\n >\n <div className={\"d-flex justify-content-between \"}>\n <div className=\"h5 text-muted mb-2 mb-sm-3\">{title}</div>\n <div\n className=\"d-flex d-sm-none cursor-pointer\"\n onClick={() =>\n setCollapseState((prevState) => ({\n ...prevState,\n [title]: !prevState[title],\n }))\n }\n >\n {!collapseState[title] ? (\n <i class=\"bi bi-chevron-up h4\"></i>\n ) : (\n <i class=\"bi bi-chevron-down h4\"></i>\n )}\n </div>\n </div>\n <div className={!collapseState[title] ? \"\" : \"d-none\"}>{children}</div>\n </div>\n );\n};\n\nconst CategoryDropdown = useMemo(() => {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.CategoryDropdown\"}\n props={{\n selectedValue: category,\n onChange: setCategory,\n }}\n />\n );\n}, [draftProposalData]);\n\nconst TitleComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: title,\n onBlur: (e) => {\n setTitle(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n max: 80,\n required: true,\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nconst SummaryComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: summary,\n multiline: true,\n onBlur: (e) => {\n setSummary(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n max: 500,\n required: true,\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nconst DescriptionComponent = useMemo(() => {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Compose\"}\n props={{\n data: description,\n onChange: setDescription,\n autocompleteEnabled: true,\n autoFocus: false,\n showProposalIdAutoComplete: true,\n }}\n />\n );\n}, [draftProposalData]);\n\nconst ConsentComponent = useMemo(() => {\n return (\n <div className=\"d-flex flex-column gap-2\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Checkbox\"}\n props={{\n value: \"toc\",\n label: (\n <>\n I’ve agree to{\" \"}\n <a\n href={\n \"https://docs.google.com/document/d/1nRGy7LhpLj56SjN9MseV1x-ubH8O_c6B9DOAZ9qTwMU/edit?usp=sharing\"\n }\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n DevHub’s Terms and Conditions\n </a>\n and commit to honoring it\n </>\n ),\n isChecked: consent.toc,\n onClick: (value) =>\n setConsent((prevConsent) => ({\n ...prevConsent,\n toc: value,\n })),\n }}\n />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Checkbox\"}\n props={{\n value: \"coc\",\n label: (\n <>\n I’ve read{\" \"}\n <a\n href={\n \"https://docs.google.com/document/d/1c6XV8Sj_BRKw8jnTIsjdLPPN6Al5eEStt1ZLYSuqw9U/edit\"\n }\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n DevHub’s Code of Conduct\n </a>\n and commit to honoring it\n </>\n ),\n isChecked: consent.coc,\n onClick: (value) =>\n setConsent((prevConsent) => ({\n ...prevConsent,\n coc: value,\n })),\n }}\n />\n </div>\n );\n}, [draftProposalData]);\n\nconst ProfileComponent = useMemo(() => {\n return (\n <Widget\n src=\"mob.near/widget/Profile.ShortInlineBlock\"\n props={{\n accountId: author,\n }}\n />\n );\n}, []);\n\nconst LinkedProposalsComponent = useMemo(() => {\n return (\n <div className=\"d-flex flex-column gap-1\">\n <div className=\"text-muted w-100 text-sm\">\n Link any relevant proposals (e.g. previous milestones).\n </div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.proposal.LinkedProposalsDropdown\"\n props={{\n onChange: setLinkedProposals,\n linkedProposals: linkedProposals,\n }}\n />\n </div>\n );\n}, [draftProposalData]);\n\nconst ReceiverAccountComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: receiverAccount,\n placeholder: devdaoAccount,\n onUpdate: setReceiverAccount,\n }}\n />\n );\n}, [draftProposalData]);\n\nconst AmountComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: requestedSponsorshipAmount,\n onChange: (e) => {\n setRequestedSponsorshipAmount(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n type: \"text\",\n prefix: \"$\",\n inputmode: \"numeric\",\n pattern: \"[0-9]*\",\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nconst CurrencyComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: tokensOptions,\n selectedValue: requestedSponsorshipToken,\n onUpdate: (v) => {\n setRequestedSponsorshipToken(v);\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nconst SponsorComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: requestedSponsor,\n placeholder: \"DevDAO\",\n onUpdate: setRequestedSponsor,\n }}\n />\n );\n}, [draftProposalData]);\n\nconst SupervisorComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: supervisor,\n onUpdate: setSupervisor,\n }}\n />\n );\n}, [draftProposalData]);\n\nif (showProposalPage) {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.Proposal\"}\n props={{ id: proposalId, ...props }}\n />\n );\n} else\n return (\n <Container className=\"w-100 py-4 px-0 px-sm-2 d-flex flex-column gap-3\">\n <Heading className=\"px-2 px-sm-0\">\n {isEditPage ? \"Edit\" : \"Create\"} Proposal\n </Heading>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.ConfirmReviewModal\"}\n props={{\n isOpen: isReviewModalOpen,\n onCancelClick: () => setReviewModal(false),\n onReviewClick: () => {\n setReviewModal(false);\n cleanDraft();\n onSubmit({ isDraft: false });\n },\n }}\n />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.ConfirmCancelModal\"}\n props={{\n isOpen: isCancelModalOpen,\n onCancelClick: () => setCancelModal(false),\n onConfirmClick: () => {\n setCancelModal(false);\n onSubmit({ isCancel: true });\n },\n }}\n />\n <div className=\"card no-border rounded-0 px-2 p-lg-0 full-width-div\">\n <div className=\"container-xl py-4 d-flex flex-wrap gap-6 w-100\">\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-2 w-100 order-2 order-md-1\"\n >\n <div className=\"d-flex gap-2 w-100\">\n <div className=\"d-none d-sm-flex\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: author,\n }}\n />\n </div>\n <div className=\"d-flex flex-column gap-4 w-100\">\n <InputContainer\n heading=\"Category\"\n description={\n <>\n Select the category that best aligns with your\n contribution to the NEAR developer community. Need\n guidance? See{\" \"}\n <a\n href={FundingDocs}\n className=\"text-decoration-underline no-space\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Funding Docs\n </a>\n .\n </>\n }\n >\n {CategoryDropdown}\n </InputContainer>\n <InputContainer\n heading=\"Title\"\n description=\"Highlight the essence of your proposal in a few words. This will appear on your proposal’s detail page and the main proposal feed. Keep it short, please :)\"\n >\n {TitleComponent}\n </InputContainer>\n <InputContainer\n heading=\"Summary\"\n description=\"Explain your proposal briefly. This is your chance to make a good first impression on the community. Include what needs or goals your work will address, your solution, and the benefit for the NEAR developer community.\"\n >\n {SummaryComponent}\n </InputContainer>\n <InputContainer\n heading=\"Description\"\n description={\n <>\n Expand on your summary with any relevant details like your\n contribution timeline, key milestones, team background,\n and a clear breakdown of how the funds will be used.\n Proposals should be simple and clear (e.g. 1 month). For\n more complex projects, treat each milestone as a separate\n proposal. Need more guidance?\n <a\n href={FundingDocs}\n className=\"text-decoration-underline no-space\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n See Funding Docs\n </a>\n .\n </>\n }\n >\n {DescriptionComponent}\n </InputContainer>\n <InputContainer heading=\"Final Consent\">\n {ConsentComponent}\n </InputContainer>\n <div className=\"d-flex justify-content-between gap-2 align-items-center\">\n <div>\n {isEditPage && (\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"btn-outline-danger shadow-none border-0 btn-sm\",\n },\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 onClick: () => setCancelModal(true),\n }}\n />\n )}\n </div>\n <div className=\"d-flex gap-2\">\n <Link\n to={\n isEditPage\n ? href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"proposal\",\n id: parseInt(id),\n },\n })\n : href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"proposals\",\n },\n })\n }\n >\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"d-flex h-100 text-muted fw-bold btn-outline shadow-none border-0 btn-sm\",\n },\n label: \"Discard Changes\",\n onClick: cleanDraft,\n }}\n />\n </Link>\n <SubmitBtn />\n </div>\n </div>\n </div>\n </div>\n </div>\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-1 w-100 order-1 order-md-2\"\n >\n <CollapsibleContainer noPaddingTop={true} title=\"Author Details\">\n <div className=\"d-flex flex-column gap-3 gap-sm-4\">\n <InputContainer heading=\"Author\">\n {ProfileComponent}\n </InputContainer>\n </div>\n </CollapsibleContainer>\n <div className=\"my-2\">\n <CollapsibleContainer title=\"Link Proposals (Optional)\">\n {LinkedProposalsComponent}\n </CollapsibleContainer>\n </div>\n <div className=\"my-2\">\n <CollapsibleContainer title=\"Funding Details\">\n <div className=\"d-flex flex-column gap-3 gap-sm-4\">\n <InputContainer\n heading=\"Recipient NEAR Wallet Address\"\n description=\"Enter the address that will receive the funds. We’ll need this to send a test transaction once your proposal is approved.\"\n >\n {ReceiverAccountComponent}\n </InputContainer>\n <InputContainer\n heading={\n <div className=\"d-flex gap-2 align-items-center\">\n Recipient Verification Status\n <div className=\"custom-tooltip\">\n <i class=\"bi bi-info-circle-fill\"></i>\n <span class=\"tooltiptext\">\n To get approved and receive payments on our\n platform, you must complete KYC/KYB verification\n using Fractal, a trusted identity verification\n solution. This helps others trust transactions with\n your account. Click \"Get Verified\" to start. <br />\n <br />\n Once verified, your profile will display a badge,\n which is valid for 365 days from the date of your\n verification. You must renew your verification upon\n expiration OR if any of your personal information\n changes.\n </span>\n </div>\n </div>\n }\n description=\"\"\n >\n <div className=\"border border-1 p-3 rounded-2\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.proposal.VerificationStatus\"\n props={{\n receiverAccount: receiverAccount,\n showGetVerifiedBtn: true,\n imageSize: 30,\n }}\n />\n </div>\n </InputContainer>\n <InputContainer\n heading=\"Total Amount (USD)\"\n description={\n <>\n Enter the exact amount you are seeking. See\n <a\n href={FundingDocs}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Funding Documentation\n </a>\n for guidelines.\n </>\n }\n >\n {AmountComponent}\n </InputContainer>\n <InputContainer\n heading=\"Currency\"\n description=\"Select your preferred currency for receiving funds. Note: The exchange rate for NEAR tokens will be the closing rate at the day of the invoice.\"\n >\n {CurrencyComponent}\n </InputContainer>\n <InputContainer heading=\"Requested Sponsor\" description=\"\">\n {SponsorComponent}\n </InputContainer>\n <InputContainer\n heading=\"Supervisor (Optional)\"\n description=\"\"\n >\n {SupervisorComponent}\n </InputContainer>\n </div>\n </CollapsibleContainer>\n </div>\n </div>\n </div>\n </div>\n </Container>\n );\n" }, "devhub.entity.post.List": { "": "// This component implementation was forked from [IndexFeed], but it does not fully implement lazy loading.\n// While this component uses InfiniteScroll, it still loads the whole list of Post IDs in one view call.\n// The contract will need to be extended with pagination support, yet, even in the current state the page loads much faster.\n// [IndexFeed]: https://near.social/#/mob.near/widget/WidgetSource?src=mob.near/widget/IndexFeed\n\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nconst { draftState, onDraftStateChange } = VM.require(\n \"devhub.megha19.near/widget/devhub.entity.post.draft\"\n);\n\nif (!href) {\n return <p>Loading modules...</p>;\n}\n\nconst QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql/`;\n\nconst queryName =\n props.queryName ?? `bo_near_devhub_v38_posts_with_latest_snapshot`;\nconst totalQueryName =\n props.totalQueryName ??\n \"bo_near_devhub_v38_posts_with_latest_snapshot_aggregate\";\nconst query = `query DevhubPostsQuery($limit: Int = 100, $offset: Int = 0, $where: ${queryName}_bool_exp = {}) {\n ${queryName}(\n limit: $limit\n offset: $offset\n order_by: {ts: desc}\n where: $where\n ) {\n post_id\n }\n }\n`;\n\nconst totalQuery = `query DevhubTotalPostsQuery($where: ${queryName}_bool_exp = {}) {\n ${totalQueryName}(\n where: $where\n ) {\n aggregate {\n count\n }\n }\n }\n`;\n\nfunction fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `bo_near` },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n}\n\nfunction searchConditionChanged() {\n return (\n props.author != state.author ||\n props.term != state.term ||\n props.tag != state.tag ||\n props.recency != state.recency\n );\n}\n\nfunction updateSearchCondition() {\n State.update({\n author: props.author,\n term: props.term,\n tag: props.tag,\n recency: props.recency,\n loading: true,\n });\n}\n\nconst initialRenderLimit = props.initialRenderLimit ?? 3;\nconst addDisplayCount = props.nextLimit ?? initialRenderLimit;\n\nState.init({\n period: \"week\",\n totalItems: 0,\n displayCount: initialRenderLimit,\n});\n\nfunction getPostIds(tag, offset) {\n if (searchConditionChanged()) {\n updateSearchCondition();\n }\n let where = {};\n let authorId = props.author;\n let label = tag || props.tag;\n if (authorId) {\n where = { author_id: { _eq: authorId }, ...where };\n }\n if (props.term) {\n where = { description: { _ilike: `%${props.term}%` }, ...where };\n }\n if (label) {\n if (typeof label === \"string\") {\n // Handle a single label\n where = { labels: { _contains: label }, ...where };\n } else if (Array.isArray(label)) {\n // Handle an array of labels\n where = {\n labels: {\n _containsAny: label,\n },\n ...where,\n };\n }\n }\n if (!props.recency) {\n // show only top level posts\n where = { parent_id: { _is_null: true }, ...where };\n }\n\n // Don't show blog and devhub-test posts\n where = {\n _and: [\n {\n _not: {\n labels: { _contains: \"blog\" },\n parent_id: { _is_null: true },\n post_type: { _eq: \"Comment\" },\n },\n },\n {\n _not: {\n labels: { _contains: \"devhub-test\" },\n },\n },\n ],\n ...where,\n };\n\n if (!offset) {\n fetchGraphQL(totalQuery, \"DevhubTotalPostsQuery\", {\n where,\n }).then((result) => {\n const data = result.body.data[totalQueryName];\n State.update({\n totalItems: data.aggregate.count,\n });\n });\n }\n\n fetchGraphQL(query, \"DevhubPostsQuery\", {\n limit: 50,\n offset: offset ?? 0,\n where,\n }).then((result) => {\n if (result.status === 200) {\n if (result.body.data) {\n const data = result.body.data[queryName];\n if (offset) {\n State.update({\n postIds: state.postIds.concat(data.map((p) => p.post_id)),\n loading: false,\n });\n } else {\n State.update({\n postIds: data.map((p) => p.post_id),\n loading: false,\n });\n }\n }\n } else {\n State.update({ loading: false });\n }\n });\n}\n\nif (!state.items || searchConditionChanged()) {\n getPostIds();\n}\n\nfunction defaultRenderItem(postId, additionalProps) {\n if (!additionalProps) {\n additionalProps = {};\n }\n // It is important to have a non-zero-height element as otherwise InfiniteScroll loads too many items on initial load\n return (\n <div className=\"py-2\" style={{ minHeight: \"150px\" }}>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.post.Post\"}\n props={{\n id: postId,\n expandable: true,\n defaultExpanded: false,\n isInList: true,\n draftState,\n isPreview: false,\n onDraftStateChange,\n ...additionalProps,\n referral: postId,\n updateTagInParent: (tag) => {\n if (typeof props.updateTagInput === \"function\") {\n props.updateTagInput(tag);\n }\n getPostIds(tag);\n },\n transactionHashes: props.transactionHashes,\n }}\n />\n </div>\n );\n}\n\nconst renderItem = props.renderItem ?? defaultRenderItem;\n\nconst cachedRenderItem = (item, i) => {\n if (props.term) {\n return renderItem(item, {\n searchKeywords: [props.term],\n });\n }\n\n const key = JSON.stringify(item);\n\n if (!(key in state.cachedItems)) {\n state.cachedItems[key] = renderItem(item);\n State.update();\n }\n return state.cachedItems[key];\n};\n\nconst ONE_DAY = 60 * 60 * 24 * 1000;\nconst ONE_WEEK = 60 * 60 * 24 * 1000 * 7;\nconst ONE_MONTH = 60 * 60 * 24 * 1000 * 30;\n\nfunction getHotnessScore(post) {\n //post.id - shows the age of the post, should grow exponentially, since newer posts are more important\n //post.likes.length - linear value\n const age = Math.pow(post.id, 5);\n const comments = post.comments;\n const commentAge = comments.reduce((sum, age) => sum + Math.pow(age, 5), 0);\n const totalAge = age + commentAge;\n //use log functions to make likes score and exponentially big age score close to each other\n return Math.log10(post.likes.length) + Math.log(Math.log10(totalAge));\n}\n\nconst getPeriodText = (period) => {\n let text = \"Last 24 hours\";\n if (period === \"week\") {\n text = \"Last week\";\n }\n if (period === \"month\") {\n text = \"Last month\";\n }\n return text;\n};\n\nlet postIds = state.postIds ?? null;\n\nconst loader = (\n <div className=\"loader\" key={\"loader\"}>\n <span\n className=\"spinner-grow spinner-grow-sm me-1\"\n role=\"status\"\n aria-hidden=\"true\"\n />\n Loading ...\n </div>\n);\n\nif (postIds === null) {\n return loader;\n}\nconst initialItems = postIds;\n\nconst jInitialItems = JSON.stringify(initialItems);\nif (state.jInitialItems !== jInitialItems) {\n // const jIndex = JSON.stringify(index);\n // if (jIndex !== state.jIndex) {\n State.update({\n jIndex,\n jInitialItems,\n items: initialItems,\n cachedItems: {},\n });\n}\n\nconst makeMoreItems = () => {\n State.update({\n displayCount: state.displayCount + addDisplayCount,\n });\n if (\n state.items.length - state.displayCount < addDisplayCount * 5 &&\n !state.loading\n ) {\n State.update({ loading: true });\n getPostIds(null, state.items.length);\n }\n};\n\nconst items = state.items ? state.items.slice(0, state.displayCount) : [];\nconst renderedItems = items.map(cachedRenderItem);\n\nconst Head =\n props.recency == \"hot\" ? (\n <div class=\"row\">\n <div class=\"fs-5 col-6 align-self-center\">\n <i class=\"bi-fire\"></i>\n <span>Hottest Posts</span>\n </div>\n <div class=\"col-6 dropdown d-flex justify-content-end\">\n <a\n class=\"btn btn-secondary dropdown-toggle\"\n href=\"#\"\n role=\"button\"\n id=\"dropdownMenuLink\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n >\n {getPeriodText(state.period)}\n </a>\n\n <ul class=\"dropdown-menu\" aria-labelledby=\"dropdownMenuLink\">\n <li>\n <button\n class=\"dropdown-item\"\n onClick={() => {\n State.update({ period: \"day\" });\n }}\n >\n {getPeriodText(\"day\")}\n </button>\n </li>\n <li>\n <button\n class=\"dropdown-item\"\n onClick={() => {\n State.update({ period: \"week\" });\n }}\n >\n {getPeriodText(\"week\")}\n </button>\n </li>\n <li>\n <button\n class=\"dropdown-item\"\n onClick={() => {\n State.update({ period: \"month\" });\n }}\n >\n {getPeriodText(\"month\")}\n </button>\n </li>\n </ul>\n </div>\n </div>\n ) : (\n <></>\n );\n\nreturn (\n <>\n {Head}\n {state.loading ? loader : null}\n {is_edit_or_add_post_transaction ? (\n <p class=\"text-secondary mt-4\">\n Post {transaction_method_name == \"edit_post\" ? \"edited\" : \"added\"}{\" \"}\n successfully. Back to{\" \"}\n <Link\n style={{\n color: \"#3252A6\",\n }}\n className=\"fw-bold\"\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"feed\" },\n })}\n >\n feed\n </Link>\n </p>\n ) : state.items.length > 0 ? (\n <div style={{ overflow: \"auto\", height: \"60vh\" }}>\n <InfiniteScroll\n pageStart={0}\n loadMore={makeMoreItems}\n hasMore={state.totalItems > state.items.length}\n loader={loader}\n useWindow={false}\n >\n {renderedItems}\n </InfiniteScroll>\n </div>\n ) : (\n <p class=\"text-secondary\">\n No posts{\" \"}\n {props.term || props.tag || props.author ? \"matches search\" : \"\"}\n {props.recency === \"hot\"\n ? \" in \" + getPeriodText(state.period).toLowerCase()\n : \"\"}\n </p>\n )}\n </>\n);\n" }, "devhub.entity.proposal.History": { "": "/*\n---props---\nprops.id: number;\nprops.newTab: boolean;\nprops.timestamp: number;\nprops.referral: any;\n*/\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || {\n href: () => {},\n};\nconst { readableDate } = VM.require(\n \"devhub.megha19.near/widget/core.lib.common\"\n) || { readableDate: () => {} };\nconst proposalId = props.id ?? (props.id ? parseInt(props.id) : 0);\nconst proposal = Near.view(\"devhub.near\", \"get_proposal\", {\n proposal_id: proposalId,\n});\nif (!proposal || !proposal.snapshot_history) {\n return <div class=\"bi bi-clock-history px-2\"></div>;\n}\nconst referral = props.referral;\n\nconst currentTimestamp = props.timestamp ?? proposal.snapshot.timestamp;\nconst snapshot = proposal.snapshot;\nconst snapshotHistory = proposal.snapshot_history\n ? Array.from(proposal.snapshot_history)\n : [];\n\nsnapshotHistory.push(snapshot);\nsnapshotHistory.reverse();\n\nconst history = (\n <div class=\"btn-group\" role=\"group\">\n <a\n class=\"card-link\"\n role=\"button\"\n title=\"proposal History\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n type=\"button\"\n >\n <div class=\"bi bi-clock-history px-2\"></div>\n </a>\n <ul class=\"dropdown-menu\">\n <a\n class=\"d-flex text-muted\"\n style={{ fontSize: \"11px\", textDecoration: \"none\", cursor: \"default\" }}\n >\n <a\n style={{\n textAlign: \"center\",\n minWidth: \"250px\",\n maxWidth: \"250px\",\n }}\n >\n Edit History\n </a>\n <a style={{ marginRight: \"8px\" }}>Compare</a>\n </a>\n {snapshotHistory.map((item) => {\n if (item === undefined) return;\n return (\n <li style={{ display: \"flex\" }}>\n <div\n style={{\n minWidth: \"250px\",\n maxWidth: \"250px\",\n }}\n >\n <a\n class=\"dropdown-item\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget\",\n params: {\n page: \"proposal\",\n id: proposalId,\n timestamp: item.timestamp,\n compareTimestamp: null,\n referral,\n },\n })}\n target={props.newTab ? \"_blank\" : undefined}\n >\n {readableDate(item.timestamp / 1000000)}\n\n <Widget\n src=\"mob.near/widget/ProfileImage\"\n props={{\n accountId: item.editor_id,\n style: {\n width: \"1.25em\",\n height: \"1.25em\",\n },\n imageStyle: {\n transform: \"translateY(-12.5%)\",\n },\n }}\n />\n {proposal.author_id.substring(0, 8)}\n </a>\n </div>\n <a\n class=\"dropdown-item\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"proposal\",\n id: proposalId,\n timestamp: currentTimestamp,\n compareTimestamp: item.timestamp,\n referral,\n },\n })}\n >\n <i class=\"bi bi-file-earmark-diff\" />\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n);\n\nreturn history;\n" }, "devhub.entity.proposal.Proposal": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || {\n href: () => {},\n};\nconst { readableDate } = VM.require(\n \"devhub.megha19.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.megha19.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 proposalURL = `https://near.org/devhub.megha19.near/widget/app?page=proposal&id=${proposal.id}&timestamp=${snapshot.timestamp}`;\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 },\n },\n {\n label: \"Approved\",\n value: {\n status: TIMELINE_STATUS.APPROVED,\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 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 },\n },\n {\n label: \"Canceled\",\n value: {\n status: TIMELINE_STATUS.CANCELED,\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Payment-processing\",\n value: {\n status: TIMELINE_STATUS.PAYMENT_PROCESSING,\n kyc_verified: false,\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 const link = `https://near.org/devhub.near/widget/app?page=proposal&id=${item.id}`;\n return (\n <a href={link} target=\"_blank\" rel=\"noopener noreferrer\">\n <div className=\"d-flex gap-2\">\n <Widget\n src={\"devhub.megha19.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 <LinkProfile account={item.snapshot.name}>\n <b className=\"text-truncate\">{item.snapshot.name}</b>\n </LinkProfile>\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 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 = { labels: [], body: body, id: proposal.id };\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_timeline\",\n args: {\n id: proposal.id,\n timeline: timeline,\n },\n gas: 270000000000000,\n },\n ]);\n};\n\nconst [isReviewModalOpen, setReviewModal] = useState(false);\nconst [isCancelModalOpen, setCancelModal] = 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 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.megha19.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={\"devhub.megha19.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.megha19.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=\"near/widget/ShareButton\"\n props={{\n postType: \"post\",\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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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 mb-4\">\n DESCRIPTION\n </div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"\n props={{ text: snapshot.description }}\n />\n\n <div className=\"d-flex gap-2 align-items-center mt-4\">\n <Widget\n src=\"devhub.megha19.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.megha19.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.megha19.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.megha19.near/widget/devhub.entity.proposal.ComposeComment\"\n }\n props={{\n ...props,\n item: item,\n notifyAccountId: authorId,\n id: proposal.id,\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.megha19.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.megha19.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 </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={<div className=\"fw-bold\">Approved</div>}\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={updatedProposalStatus.value.kyc_verified}\n label=\"Sponsor verifies KYC/KYB\"\n disabled={selectedStatusIndex !== 6}\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 <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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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 <Widget\n src={\n \"devhub.megha19.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Save\",\n disabled:\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.components.molecule.Spinner": { "": "return (\n <img\n style={{ height: \"auto\", width: \"50px\" }}\n src={\"https://i.gifer.com/origin/34/34338d26023e5515f6cc8969aa027bca.gif\"}\n alt=\"loader\"\n />\n);\n" }, "devhub.feature.proposal-search.by-input": { "": "const InputContainer = styled.div`\n display: flex;\n flex-direction: row;\n position: relative;\n width: 100%;\n`;\n\nconst [search, setSearch] = useState(props.search);\nconst onSearch = props.onSearch ?? (() => {});\nconst onEnter = props.onEnter ?? (() => {});\n\nconst updateInput = (value) => setSearch(value);\n\nuseEffect(() => {\n if (search !== props.search) {\n onSearch(search);\n }\n}, [search]);\n\nconst className = props.className ?? \"\";\n\nreturn (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1 w-100\" + className,\n value: search,\n onChange: (e) => {\n updateInput(e.target.value);\n },\n onKeyDown: (e) => e.key == \"Enter\" && onEnter(),\n skipPaddingGap: true,\n placeholder: \"Search by content\",\n inputProps: {\n prefix: <i class=\"bi bi-search m-auto\"></i>,\n },\n }}\n />\n);\n" }, "devhub.entity.team.Configurator": { "": "const { Tile } =\n VM.require(\"devhub.megha19.near/widget/devhub.components.molecule.Tile\") ||\n (() => <></>);\n\nif (!Tile) {\n return <div>Loading...</div>;\n}\n\nconst { data, onSubmit, onCancel } = props;\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n`;\n\nconst Item = styled.div`\n padding: 10px;\n margin: 5px;\n display: flex;\n align-items: center;\n flex-direction: row;\n gap: 10px;\n`;\n\nconst backwardsCompatibleLabel = (oldLabel) => {\n if (typeof oldLabel === \"string\")\n return oldLabel.startsWith(\"starts-with:\") ? oldLabel.slice(12) : oldLabel;\n else return \"\";\n};\nconst backwardsCompatibleTeam = (oldTeam) => {\n if (typeof oldTeam === \"string\")\n return oldTeam.startsWith(\"team:\") ? oldTeam.slice(5) : oldTeam;\n else return \"\";\n};\n\nconst initialData = data.members || [];\nconst [newItem, setNewItem] = useState(\"\");\nconst [teamName, setTeamName] = useState(\n backwardsCompatibleTeam(data.teamName) || \"\"\n);\nconst [label, setLabel] = useState(data.label || \"\");\nconst [labelType, setLabelType] = useState(\n (data.label || \"\").startsWith(\"starts-with:\") ? \"starts-with:\" : \"\"\n);\nconst [editPost, setEditPost] = useState(data.editPost || false);\nconst [useLabels, setUseLabels] = useState(data.useLabels || false);\nconst [members, setMembers] = useState(initialData || []);\n\nconst [showPreview, setShowPreview] = useState(data.showPreview || []);\n\nconst [warning, setWarning] = useState(\"\");\n\nconst handleAddItem = () => {\n if (newItem) {\n setMembers([...members, newItem]);\n setNewItem(\"\");\n }\n};\n\nconst handleDeleteItem = (index) => {\n const updatedData = [...members];\n updatedData.splice(index, 1);\n setMembers(updatedData);\n};\n\nconst handleSubmit = () => {\n if (newItem !== \"\") {\n return setWarning(\n \"Don't forget to add the last member or clear the field to get rid of this warning.\"\n );\n }\n if (teamName && teamName.startsWith(\"team:\")) {\n return setWarning(\"The team name can't start with 'team:'\");\n }\n if (\n !backwardsCompatibleLabel(label) ||\n !label.trim() ||\n label === \"starts-with:\"\n ) {\n return setWarning(\"Invalid label, make sure it's not taken\");\n }\n if (members.length < 1) {\n return setWarning(\"Add at least one member to the team\");\n }\n\n onSubmit({\n teamName,\n label: labelType + backwardsCompatibleLabel(label),\n editPost,\n useLabels,\n members: members.map((member) => member.trim()),\n });\n};\n\nreturn (\n <Tile className=\"p-3\">\n <Container>\n <h3>{data.teamName == \"\" ? \"Edit label\" : \"Create label\"}</h3>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.atom.Alert\"\n props={{\n onClose: () => setWarning(\"\"),\n message: warning,\n }}\n />\n {/* Moderators is only editable through the CLI except for the members property */}\n {teamName !== \"moderators\" && (\n <>\n <div className=\"flex-grow-1\">\n <span>Group name</span>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n skipPaddingGap: true,\n onChange: (e) => setTeamName(e.target.value),\n value: teamName,\n placeholder: \"Team name\",\n }}\n />\n </div>\n\n <div className=\"flex-grow-1\">\n <div>\n Would you like this group to limit their restrictions to a single\n label, or would you prefer them to restrict it with any label that\n follows a similar convention?\n </div>\n <div className=\"col-lg-6 mb-2\">\n <select\n onChange={(event) => setLabelType(event.target.value)}\n class=\"form-select\"\n aria-label=\"Select type\"\n value={labelType}\n >\n <option value=\"starts-with:\">\n Restrict multiple labels with a common prefix\n </option>\n <option value=\"\">Restrict a single label</option>\n </select>\n <div>What would you like the restricted label to be?</div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n onChange: (e) => setLabel(e.target.value),\n value: backwardsCompatibleLabel(label),\n skipPaddingGap: true,\n placeholder: \"label\",\n inputProps: {\n prefix: labelType,\n },\n }}\n />\n <div>Select label permissions</div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.team.LabelPermissions\"\n props={{\n identifier: data.teamName,\n editPost,\n setEditPost,\n useLabels,\n setUseLabels,\n disabled: false,\n }}\n />\n </div>\n </div>\n </>\n )}\n {members.map((item, index) => (\n <Item key={index}>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: item,\n skipPaddingGap: true,\n placeholder: \"member\",\n inputProps: {\n prefix: \"member\",\n disabled: true,\n },\n }}\n />\n </div>\n <button\n className=\"btn btn-outline-danger\"\n onClick={() => handleDeleteItem(index)}\n >\n <i className=\"bi bi-trash-fill\" />\n </button>\n </Item>\n ))}\n <Item>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n skipPaddingGap: true,\n onChange: (e) => setNewItem(e.target.value),\n value: newItem,\n placeholder: \"member\",\n inputProps: {\n prefix: \"member\",\n },\n }}\n />\n </div>\n <button\n className=\"btn btn-success add-member\"\n onClick={handleAddItem}\n disabled={newItem === \"\"}\n >\n <i className=\"bi bi-plus\" />\n </button>\n </Item>\n <div\n className={\"d-flex align-items-center justify-content-end gap-3 mt-4\"}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-danger shadow-none border-0\" },\n label: \"Cancel\",\n onClick: () => onCancel(),\n }}\n />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-success\" },\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Submit\",\n onClick: handleSubmit,\n }}\n />\n </div>\n </Container>\n </Tile>\n);\n" }, "devhub.entity.addon.github.kanban_board": { "": "const { DataRequest } = VM.require(\n \"devhub.megha19.near/widget/core.lib.data-request\"\n);\nDataRequest || (DataRequest = { paginated: () => {} });\n\nconst resPerPage = 100;\n\nfunction extractOwnerAndRepo(url) {\n // Remove any leading or trailing slashes and split the URL by \"/\"\n const parts = url\n .trim()\n .replace(/^\\/+|\\/+$/g, \"\")\n .split(\"/\");\n\n // Check if the URL matches the GitHub repository format\n if (parts.length === 5 && parts[2] === \"github.com\") {\n const owner = parts[3];\n const repo = parts[4];\n return { owner, repo };\n } else {\n return null;\n }\n}\n\nconst GithubKanbanBoard = ({\n columns,\n title,\n description,\n repoURL,\n ticketState,\n dataTypesIncluded,\n metadata,\n}) => {\n State.init({\n ticketsLastPage: false,\n fetchedTicketsCount: {},\n ticketsByColumn: {},\n cachedItems: {},\n displayCount: 40,\n noTicketsFound: false,\n error: null,\n });\n\n const ticketStateFilter = ticketState ?? \"all\";\n\n function fetchTickets(columnId, labelSearchTerms, allLabelsMust) {\n const pageNumber = !state.fetchedTicketsCount[columnId]\n ? 1\n : state.fetchedTicketsCount[columnId] / resPerPage + 1;\n const { repo, owner } = extractOwnerAndRepo(repoURL);\n const type =\n dataTypesIncluded.issue && dataTypesIncluded.pullRequest\n ? \"\"\n : dataTypesIncluded.issue\n ? \"type:issue\"\n : \"type:pr\";\n const labels = allLabelsMust\n ? (labelSearchTerms ?? []).map((item) => `label:${item}`).join(\" \")\n : `label:${(labelSearchTerms ?? []).join(\",\")}`;\n const state =\n ticketStateFilter === \"all\" ? \"\" : `state:${ticketStateFilter}`;\n const q = encodeURIComponent(\n `${labels} repo:${owner}/${repo} ${state} ${type}`\n );\n const res = fetch(\n `https://api.github.com/search/issues?per_page=${resPerPage}&page=${pageNumber}&q=${q}`\n );\n\n if (res !== null) {\n if (res.status !== 200) {\n State.update({\n error:\n \"The listed users and repositories cannot be searched either because the resources do not exist or you do not have permission to view them.\",\n });\n } else {\n if (!res.body.incomplete_results) {\n State.update({\n ticketsLastPage: true,\n });\n }\n if (res.body.total_count === 0) {\n State.update({\n noTicketsFound: true,\n });\n } else {\n State.update((lastKnownState) => ({\n ...lastKnownState,\n fetchedTicketsCount: {\n ...lastKnownState.fetchedTicketsCount,\n [columnId]:\n lastKnownState.fetchedTicketsCount[columnId] ?? 0 + resPerPage,\n },\n ticketsByColumn: {\n ...lastKnownState.ticketsByColumn,\n [columnId]: [\n ...(lastKnownState?.ticketsByColumn?.[columnId] ?? []),\n ...res.body.items,\n ],\n },\n }));\n }\n }\n }\n }\n\n if (\n repoURL &&\n Object.keys(state.ticketsByColumn).length !== Object.keys(columns).length\n ) {\n Object.keys(columns).map((item) => {\n const columnId = item;\n const columnData = columns[columnId];\n fetchTickets(\n columnId,\n columnData?.labelSearchTerms,\n columnData.allLabelsMust\n );\n });\n }\n\n const renderItem = (ticket) => (\n <Widget\n src={`devhub.megha19.near/widget/devhub.entity.addon.${metadata.ticket.type}`}\n props={{ metadata: metadata.ticket, payload: ticket }}\n key={ticket.id}\n />\n );\n\n const cachedRenderItem = (item, index) => {\n const key = JSON.stringify(item);\n\n if (!(key in state.cachedItems)) {\n state.cachedItems[key] = renderItem(item, index);\n State.update();\n }\n return state.cachedItems[key];\n };\n\n const makeMoreItems = (columnId, labelSearchTerms, allLabelsMust) => {\n const addDisplayCount = 20;\n const newDisplayCount = state.displayCount + addDisplayCount;\n State.update({\n displayCount: newDisplayCount,\n });\n if (state.fetchedTicketsCount[columnId] < 2 * newDisplayCount) {\n fetchTickets(columnId, labelSearchTerms, allLabelsMust);\n }\n };\n\n return (\n <div>\n <div className=\"d-flex flex-column align-items-center gap-2 pb-4\">\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n <span>{title}</span>\n </h5>\n\n <p className=\"m-0 py-1 text-secondary text-center\">{description}</p>\n </div>\n\n <div className=\"d-flex gap-3 w-100\" style={{ overflowX: \"auto\" }}>\n {Object.keys(columns).length === 0 ? (\n <div\n className={[\n \"d-flex align-items-center justify-content-center\",\n \"w-100 text-black-50 opacity-50\",\n ].join(\" \")}\n style={{ height: 384 }}\n >\n No columns were created so far.\n </div>\n ) : null}\n {Object.values(columns ?? {})?.map((column) => {\n const tickets = state.ticketsByColumn[column.id]\n ? state.ticketsByColumn[column.id].slice(0, state.displayCount)\n : [];\n const renderedItems = tickets.map(cachedRenderItem);\n\n return (\n <div\n className=\"col-3\"\n style={{ minWidth: \"300px\" }}\n key={`column-${column.id}-view`}\n >\n <div className=\"card rounded-4\">\n <div\n style={{ height: \"75vh\", overflow: \"auto\" }}\n className={[\n \"card-body d-flex flex-column gap-3 p-2\",\n \"border border-1 rounded-4\",\n ].join(\" \")}\n id={column.id}\n >\n <span className=\"d-flex flex-column py-1\">\n <h6 className=\"card-title h6 m-0\">{column.title}</h6>\n <p class=\"text-secondary m-0\">{column.description}</p>\n </span>\n {state.error && (\n <div className=\"alert alert-danger\">\n Error: {state.error}\n </div>\n )}\n {state.noTicketsFound && <p>No tickets found</p>}\n {state.fetchedTicketsCount[column.id] > 0 && (\n <InfiniteScroll\n loadMore={() =>\n makeMoreItems(\n column.id,\n column?.labelSearchTerms,\n column.allLabelsMust\n )\n }\n hasMore={!state.ticketsLastPage}\n loader={<>Loading...</>}\n useWindow={false}\n threshold={80}\n >\n <div class=\"d-flex flex-column gap-2\">\n {renderedItems}\n </div>\n </InfiniteScroll>\n )}\n </div>\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n};\n\nreturn GithubKanbanBoard(props);\n" }, "devhub.components.island.participate": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nhref || (href = () => {});\n\nconst Container = styled.div`\n margin-top: 2.25rem;\n\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n width: 100%;\n\n @media screen and (max-width: 768px) {\n flex-direction: column;\n margin-top: 0;\n }\n`;\n\nconst LinkItem = styled.a`\n color: #00ec97;\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n\n display: flex;\n align-items: center;\n\n &:hover {\n text-decoration: none;\n color: #096d50;\n }\n`;\n\nconst Links = [\n {\n links: [\n {\n title: \"Ideate on DevHub\",\n href: \"/devhub.near/widget/app?page=blog&id=2029\",\n count: 1,\n },\n {\n title: \"Post a Proposal\",\n href: \"/devhub.near/widget/app?page=blog&id=2035\",\n count: 2,\n },\n {\n title: \"Host an Event\",\n href: \"/devhub.near/widget/app?page=community&handle=hacks&tab=wiki-202\",\n count: 3,\n },\n ],\n },\n {\n links: [\n {\n title: \"Improve NEAR Docs\",\n href: \"https://github.com/near/docs\",\n count: 4,\n },\n {\n title: \"Join the Fellowship\",\n href: \"/devhub.near/widget/app?page=community&handle=fellowship&tab=wiki-201\",\n count: 5,\n },\n {\n title: \"Join NEAR Campus\",\n href: \"/devhub.near/widget/app?page=community&handle=near-campus\",\n count: 6,\n },\n ],\n },\n {\n links: [\n {\n title: \"Dive into Hackbox\",\n href: \"/hackbox.near/widget/home\",\n count: 7,\n },\n ],\n },\n];\n\nconst CTA = styled.a`\n display: flex;\n padding: 0.875rem 1rem;\n align-items: center;\n gap: 0.5rem;\n\n border-radius: 1rem;\n background: #00ec97;\n\n color: #f4f4f4 !important;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.03rem;\n\n width: max-content;\n margin-top: 1.5rem;\n\n &:hover {\n background: #555555;\n text-decoration: none !important;\n }\n\n @media screen and (max-width: 768px) {\n color: #f4f4f4 !important;\n font-size: 20px;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 24px */\n\n display: flex;\n padding: 14px 16px;\n align-items: center;\n gap: 8px;\n\n border-radius: 16px;\n background: #555555;\n\n &:hover {\n //background: #555555;\n text-decoration: none;\n }\n }\n`;\n\nconst SectionPadding = styled.div`\n padding: 3rem;\n padding-top: 0;\n\n display: flex;\n flex-direction: column;\n\n @media screen and (max-width: 768px) {\n padding: 1rem;\n padding-top: 0;\n }\n`;\n\nconst LinksContainer = styled.div`\n width: 30%;\n\n @media screen and (max-width: 768px) {\n width: 100%;\n margin-bottom: 1rem;\n }\n`;\n\nconst Content = (\n <SectionPadding>\n <Container>\n {Links.map((it) => (\n <LinksContainer key={Math.random()}>\n <div className=\"d-flex flex-column gap-3 gap-md-2\">\n {it.links.map((link) => (\n <div className=\"d-flex flex-row\">\n <span\n style={{\n color: \"#555555\",\n border: \"2px #555555 solid\",\n fontSize: 12,\n padding: 4,\n width: 22,\n height: 22,\n }}\n className=\"rounded-circle d-flex align-items-center justify-content-center me-1\"\n >\n {link.count}\n </span>{\" \"}\n <LinkItem href={link.href} target=\"no_blank\">\n {link.title}\n </LinkItem>\n </div>\n ))}\n </div>\n </LinksContainer>\n ))}\n </Container>\n\n <Link\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"contribute\" },\n })}\n style={{ textDecoration: \"none\" }}\n >\n <CTA>Learn more →</CTA>\n </Link>\n </SectionPadding>\n);\n\nreturn (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.island.home-section\"\n props={{\n title: \"/participate\",\n titleColor: \"#555555\",\n description:\n \"There are many ways to start your contribution journey. You can:\",\n children: Content,\n }}\n />\n);\n" }, "devhub.entity.community.Announcements": { "": "const { handle } = props;\nconst { getCommunity, setCommunitySocialDB } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\ngetCommunity = getCommunity || (() => <></>);\nsetCommunitySocialDB = setCommunitySocialDB || (() => <></>);\n\nconst communityData = getCommunity({ handle });\nconst [postsExists, setPostExists] = useState(false);\nconst [newUnseenPosts, setNewUnseenPosts] = useState([]);\nconst [lastQueryRequestTimestamp, setLastQueryRequestTimestamp] = useState(\n new Date().getTime()\n);\nconst [submittedAnnouncementData, setSubmittedAnnouncementData] =\n useState(null);\nconst communityAccountId = `${handle}.community.devhub.near`;\n\nlet checkIndexerInterval;\nconst onNewUnseenPosts = (newUnseenPosts) => {\n if (newUnseenPosts.length > 0) {\n clearInterval(checkIndexerInterval);\n }\n};\n\nuseEffect(() => {\n if (submittedAnnouncementData) {\n const checkForAnnouncementInSocialDB = () => {\n Near.asyncView(\"social.near\", \"get\", {\n keys: [`${communityAccountId}/post/**`],\n }).then((result) => {\n try {\n const submittedAnnouncementText = JSON.parse(\n submittedAnnouncementData.post.main\n ).text;\n const lastAnnouncementTextFromSocialDB = JSON.parse(\n result[communityAccountId].post.main\n ).text;\n if (submittedAnnouncementText === lastAnnouncementTextFromSocialDB) {\n setSubmittedAnnouncementData(null);\n checkIndexerInterval = setInterval(() => {\n setLastQueryRequestTimestamp(new Date().getTime());\n }, 500);\n return;\n }\n } catch (e) {}\n setTimeout(() => checkForAnnouncementInSocialDB(), 1000);\n });\n };\n checkForAnnouncementInSocialDB();\n }\n}, [submittedAnnouncementData]);\n\nconst MainContent = styled.div`\n padding-left: 2rem;\n flex: 3;\n @media screen and (max-width: 960px) {\n padding-left: 0rem;\n }\n .post:hover {\n background-color: inherit !important;\n }\n`;\n\nconst SidebarContainer = styled.div`\n flex: 1;\n`;\n\nconst Heading = styled.div`\n font-size: 19px;\n font-weight: 600;\n`;\n\nconst SubHeading = styled.div`\n font-size: 15px;\n font-weight: 600;\n`;\n\nconst Container = styled.div`\n flex-wrap: no-wrap;\n max-width: 100%;\n\n .max-width-100 {\n max-width: 100%;\n }\n @media screen and (max-width: 960px) {\n flex-wrap: wrap;\n }\n\n .card {\n border-radius: 1rem !important;\n }\n\n .display-none {\n display: none;\n }\n`;\n\nconst Tag = styled.div`\n border-top-right-radius: 50px;\n border-bottom-right-radius: 50px;\n border-top-left-radius: 50px;\n border-bottom-left-radius: 50px;\n padding-inline: 0.8rem;\n padding-block: 0.3rem;\n display: flex;\n gap: 0.5rem;\n border-width: 1px;\n border-style: solid;\n font-size: 14px;\n color: rgba(0, 236, 151, 1);\n font-weight: 800;\n`;\n\nconst [sort, setSort] = useState(\"desc\");\n\nreturn (\n <div className=\"w-100\" style={{ maxWidth: \"100%\" }}>\n <Container className=\"d-flex gap-3 m-3 pl-2\">\n <MainContent className=\"max-width-100\">\n <div className=\"d-flex flex-column gap-4\">\n {context.accountId &&\n (communityData?.admins ?? []).includes(context.accountId) && (\n <div className=\"card p-4\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.community.Compose\"}\n props={{\n onSubmit: (v) => {\n setSubmittedAnnouncementData(v);\n setCommunitySocialDB({ handle, data: v });\n },\n profileAccountId: `${handle}.community.devhub.near`,\n isFinished: () => submittedAnnouncementData === null,\n }}\n />\n </div>\n )}\n <div className=\"d-flex flex-wrap justify-content-between\">\n <Heading>Announcements</Heading>\n <div\n className={\n postsExists\n ? \"d-flex align-items-center gap-2\"\n : \" display-none\"\n }\n >\n <select\n name=\"sort\"\n id=\"sort\"\n class=\"form-select\"\n value={sort}\n onChange={(e) => {\n setSort(e.target.value);\n }}\n >\n <option selected value=\"desc\">\n Latest\n </option>\n <option value=\"recentcommentdesc\">Last Commented</option>\n </select>\n </div>\n </div>\n {!postsExists && (\n <div>\n <h6>No announcements exists.</h6>\n </div>\n )}\n <div className={postsExists && \"card p-4\"}>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.organism.Feed\"\n props={{\n filteredAccountIds: [communityAccountId],\n sort: sort,\n setPostExists: setPostExists,\n showFlagAccountFeature: true,\n lastQueryRequestTimestamp,\n onNewUnseenPosts,\n }}\n />\n </div>\n </div>\n </MainContent>\n <SidebarContainer>\n <div className=\"d-flex flex-column gap-3\">\n <div className=\"card p-4\">\n <div className=\"mb-2\">{communityData?.description}</div>\n <div className=\"d-flex gap-2 flex-wrap\">\n <Tag>{communityData?.tag} </Tag>\n </div>\n </div>\n <div className=\"card p-4 d-flex flex-column gap-2\">\n <SubHeading>Community Admins</SubHeading>\n {(communityData?.admins ?? []).map((accountId) => (\n <div\n key={accountId}\n className=\"d-flex\"\n style={{ fontWeight: 500 }}\n >\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.ProfileCard\"\n props={{ accountId }}\n />\n </div>\n ))}\n </div>\n </div>\n </SidebarContainer>\n </Container>\n </div>\n);\n" }, "devhub.entity.proposal.LikeButton": { "": "const item = props.item;\nconst proposalId = props.proposalId;\n\nif (!item) {\n return \"\";\n}\n\nconst likes = Social.index(\"like\", item);\n\nconst dataLoading = likes === null;\n\nconst likesByUsers = {};\n\n(likes || []).forEach((like) => {\n if (like.value.type === \"like\") {\n likesByUsers[like.accountId] = like;\n } else if (like.value.type === \"unlike\") {\n delete likesByUsers[like.accountId];\n }\n});\nif (state.hasLike === true) {\n likesByUsers[context.accountId] = {\n accountId: context.accountId,\n };\n} else if (state.hasLike === false) {\n delete likesByUsers[context.accountId];\n}\n\nconst accountsWithLikes = Object.keys(likesByUsers);\nconst hasLike = context.accountId && !!likesByUsers[context.accountId];\nconst hasLikeOptimistic =\n state.hasLikeOptimistic === undefined ? hasLike : state.hasLikeOptimistic;\nconst totalLikes =\n accountsWithLikes.length +\n (hasLike === false && state.hasLikeOptimistic === true ? 1 : 0) -\n (hasLike === true && state.hasLikeOptimistic === false ? 1 : 0);\n\nconst LikeButton = styled.button`\n border: 0;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n color: #687076;\n font-weight: 400;\n font-size: 14px;\n line-height: 17px;\n cursor: pointer;\n background: none;\n padding: 6px;\n transition: color 200ms;\n\n i {\n font-size: 16px;\n transition: color 200ms;\n\n &.bi-heart-fill {\n color: #e5484d !important;\n }\n }\n\n &:hover,\n &:focus {\n outline: none;\n color: #11181c;\n }\n`;\n\nconst likeClick = (e) => {\n e.preventDefault();\n e.stopPropagation();\n if (state.loading) {\n return;\n }\n\n State.update({\n loading: true,\n hasLikeOptimistic: !hasLike,\n });\n\n const data = {\n index: {\n like: JSON.stringify({\n key: item,\n value: {\n type: hasLike ? \"unlike\" : \"like\",\n },\n }),\n },\n };\n\n if (\n !hasLike &&\n props.notifyAccountId &&\n props.notifyAccountId !== context.accountId\n ) {\n if (proposalId) {\n data.index.notify = JSON.stringify({\n key: props.notifyAccountId,\n value: {\n type: \"devhub/like\",\n item,\n proposal: proposalId,\n },\n });\n } else {\n data.index.notify = JSON.stringify({\n key: props.notifyAccountId,\n value: {\n type: \"like\",\n item,\n },\n });\n }\n }\n Social.set(data, {\n onCommit: () => State.update({ loading: false, hasLike: !hasLike }),\n onCancel: () =>\n State.update({\n loading: false,\n hasLikeOptimistic: !state.hasLikeOptimistic,\n }),\n });\n};\n\nconst title = hasLike ? \"Unlike\" : \"Like\";\n\nreturn (\n <LikeButton\n disabled={state.loading || dataLoading || !context.accountId}\n title={title}\n onClick={likeClick}\n >\n <i className={`${hasLikeOptimistic ? \"bi-heart-fill\" : \"bi-heart\"}`} />\n {Object.values(likesByUsers ?? {}).length > 0 ? (\n <span className={`count ${hasLike ? \"liked\" : \"\"}`}>\n <Widget\n loading={likeCount || \"\"}\n src=\"mob.near/widget/N.Overlay.Faces\"\n props={{ accounts: likesByUsers, limit: 10 }}\n />\n </span>\n ) : (\n \"0\"\n )}\n </LikeButton>\n);\n" }, "devhub.components.molecule.DropDownWithSearch": { "": "const {\n selectedValue,\n onChange,\n options,\n defaultLabel,\n showSearch,\n searchInputPlaceholder,\n searchByLabel,\n searchByValue,\n onSearch,\n} = props;\n\nconst [searchTerm, setSearchTerm] = useState(\"\");\nconst [filteredOptions, setFilteredOptions] = useState(options);\nconst [isOpen, setIsOpen] = useState(false);\nconst [selectedOption, setSelectedOption] = useState({\n label:\n options?.find((item) => item.value === selectedValue)?.label ??\n defaultLabel,\n value: defaultLabel,\n});\n\nuseEffect(() => {\n if (selectedOption.value !== selectedValue) {\n setSelectedOption({\n label:\n options?.find((item) => item.value === selectedValue)?.label ??\n defaultLabel,\n value: defaultLabel,\n });\n }\n}, [selectedValue]);\n\nuseEffect(() => {\n setFilteredOptions(options);\n}, [options]);\n\nconst handleSearch = (event) => {\n const term = event.target.value.toLowerCase();\n setSearchTerm(term);\n if (typeof onSearch === \"function\") {\n onSearch(term);\n return;\n }\n\n const filteredOptions = options.filter((option) => {\n if (searchByLabel) {\n return option.label.toLowerCase().includes(term);\n }\n if (searchByValue) {\n return option.value.toString().toLowerCase().includes(term);\n }\n });\n\n setFilteredOptions(filteredOptions);\n};\n\nconst toggleDropdown = () => {\n setIsOpen(!isOpen);\n};\n\nconst handleOptionClick = (option) => {\n setSelectedOption(option);\n setIsOpen(false);\n onChange(option);\n};\n\nconst Container = styled.div`\n .drop-btn {\n width: 100%;\n text-align: left;\n padding-inline: 10px;\n }\n\n .dropdown-toggle:after {\n position: absolute;\n top: 46%;\n right: 5%;\n }\n\n .dropdown-menu {\n width: 100%;\n }\n\n .dropdown-item.active,\n .dropdown-item:active {\n background-color: #f0f0f0 !important;\n color: black;\n }\n\n .custom-select {\n position: relative;\n }\n\n .scroll-box {\n max-height: 200px;\n overflow-y: scroll;\n }\n\n .selected {\n background-color: #f0f0f0;\n }\n\n input {\n background-color: #f8f9fa;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n`;\nlet searchFocused = false;\nreturn (\n <Container>\n <div\n className=\"custom-select\"\n tabIndex=\"0\"\n onBlur={() => {\n setTimeout(() => {\n setIsOpen(searchFocused || false);\n }, 0);\n }}\n >\n <div className=\"dropdown-toggle bg-white border rounded-2 btn drop-btn\">\n <div\n className={`selected-option w-100 text-wrap ${\n selectedOption.label === defaultLabel ? \"text-muted\" : \"\"\n }`}\n onClick={toggleDropdown}\n >\n {selectedOption.label}\n </div>\n </div>\n\n {isOpen && (\n <div className=\"dropdown-menu dropdown-menu-end dropdown-menu-lg-start px-2 shadow show\">\n {showSearch && (\n <input\n type=\"text\"\n className=\"form-control mb-2\"\n placeholder={searchInputPlaceholder ?? \"Search options\"}\n value={searchTerm}\n onChange={handleSearch}\n onFocus={() => {\n searchFocused = true;\n }}\n onBlur={() => {\n setTimeout(() => {\n searchFocused = false;\n }, 0);\n }}\n />\n )}\n <div className=\"scroll-box\">\n {filteredOptions.map((option) => (\n <div\n key={option.value}\n className={`dropdown-item cursor-pointer w-100 text-wrap ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n onClick={() => handleOptionClick(option)}\n >\n {option.label}\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n </Container>\n);\n" }, "devhub.page.contribute": { "": "const Header = styled.div`\n //background: white;\n padding: 1rem 3.125rem;\n width: 100%;\n margin: 24px;\n\n @media screen and (max-width: 768px) {\n margin: 1rem 0;\n padding: 1rem;\n }\n`;\n\nconst PageHeader = styled.h1`\n color: #555555;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 500;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.24px;\n margin: 0;\n`;\n\nconst Lead = styled.h5`\n color: #151515;\n font-size: 2.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 48px */\n margin: 2.25rem 8rem;\n margin-top: 0;\n\n @media screen and (max-width: 768px) {\n font-size: 1.75rem;\n margin: 1rem;\n margin-top: 0;\n }\n`;\n\nconst Container = styled.div`\n padding: 2.25rem 8rem;\n width: 100%;\n //background-color: white;\n\n @media screen and (max-width: 768px) {\n padding: 1rem;\n }\n`;\n\nconst actions = [\n {\n title: \"Ideate on DevHub\",\n description:\n \"The first step in any NEAR ecosystem project is ideation. It is crucial to have a way to find people to share and explore ideas with, partly because it can save a lot of time based on prior discussions. But also because it can you gauge support from a diversity of stakeholders.\",\n ctaAction: \"Learn More →\",\n ctaLink: \"/devhub.near/widget/app?page=blog&id=2029\",\n },\n {\n title: \"Post a Proposal\",\n description:\n \"If you have already nurtured and refined your idea, you're ready to draft and post your funding proposal.This guide is here to help you craft a compelling, convincing, and concise proposal that will capture the interest of potential funders.\",\n ctaAction: \"Learn More →\",\n ctaLink: \"/devhub.near/widget/app?page=blog&id=2035\",\n },\n {\n title: \"Host an Event\",\n description:\n \"We are always on the lookout for events that align with our mission and provide value to the NEAR ecosystem. If you are organizing such an event, we would love to hear from you! Below is a guide on how to submit a sponsorship proposal to us.\",\n ctaAction: \"Learn More →\",\n ctaLink: \"/devhub.near/widget/app?page=community&handle=hacks&tab=wiki-202\",\n },\n {\n title: \"Improve NEAR Docs\",\n description:\n \"NEAR documentation is an open source repository that anyone can fork, extend and contribute to by creating pull requests. To get started, head over to our github repository and checkout how you can make your first contribution. \",\n ctaAction: \"Learn More →\",\n ctaLink: \"https://github.com/near/docs\",\n },\n {\n title: \"Join the Fellowship\",\n description:\n \"As the NEAR ecosystem grows rapidly, there is an increasing need to improve developer productivity. The DevDAO NEAR Platform Fellowship Program aims to solve this issue by providing guidance to new contributors from experienced developers.\",\n ctaAction: \"Learn More →\",\n ctaLink:\n \"/devhub.near/widget/app?page=community&handle=fellowship&tab=wiki-1\",\n },\n {\n title: \"Join NEAR Campus\",\n description:\n \"DevHub’s NEAR Campus supports existing student clubs, researchers, and faculties in blockchain technologies, enhancing both curricular and extracurricular activities. We aim to merge blockchain education with mainstream academics.\",\n ctaAction: \"Learn More →\",\n ctaLink: \"/devhub.near/widget/app?page=community&handle=near-campus\",\n },\n {\n title: \"Dive into Hackbox\",\n description:\n \"Hackbox is a revolutionary plug-and-play solution designed to empower local leads and community stewards in hosting hackathons easily and efficiently.\",\n ctaAction: \"Learn More →\",\n ctaLink: \"/hackbox.near/widget/home\",\n },\n];\n\nconst Card = styled.div`\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n\n h5 {\n color: #151515;\n font-size: 1.75rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 43.2px */\n\n display: flex;\n align-items: center;\n }\n\n p {\n color: #000;\n font-size: 1rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n }\n\n a {\n color: #f4f4f4;\n font-size: 1rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.48px;\n\n display: flex;\n padding: 0.5rem 1rem;\n align-items: center;\n gap: 8px;\n\n border-radius: 0.5rem;\n background: #00ec97;\n\n width: max-content;\n }\n`;\nconst ActionCard = ({ action, index }) => {\n return (\n <Card>\n <h5>\n <span\n style={{\n color: \"#151515\",\n border: \"2px #151515 solid\",\n fontSize: 12,\n padding: 4,\n width: 22,\n height: 22,\n }}\n className=\"rounded-circle d-flex align-items-center justify-content-center me-1\"\n >\n {index + 1}\n </span>\n {action.title}\n </h5>\n <p>{action.description}</p>\n <a href={action.ctaLink} target=\"no_blank\">\n {action.ctaAction}\n </a>\n </Card>\n );\n};\n\nconst ActionContainer = styled.div`\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n justify-content: center;\n column-gap: 1rem;\n row-gap: 2rem;\n\n width: 100%;\n\n @media screen and (max-width: 768px) {\n display: flex;\n flex-direction: column;\n }\n`;\n\nreturn (\n <>\n <Widget src={`devhub.megha19.near/widget/devhub.components.island.banner`} />\n <Header>\n <PageHeader>Contribute</PageHeader>\n </Header>\n <Lead>\n There are many ways to start your contribution journey. You can:\n </Lead>\n <Container>\n <ActionContainer>\n {actions.map((action, index) => (\n <ActionCard action={action} index={index} />\n ))}\n </ActionContainer>\n </Container>\n </>\n);\n" }, "devhub.page.post": { "": "const { id } = props;\n\nconst Container = styled.div`\n padding: 0 3rem 3rem 3rem;\n width: 100%;\n max-width: 100%;\n\n @media screen and (max-width: 768px) {\n padding: 0 1rem 1rem 1rem;\n }\n`;\n\nreturn (\n <Container>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.post.Post\"}\n props={{\n id,\n }}\n />\n </Container>\n);\n" }, "devhub.components.molecule.PostControls": { "": "const { className, title, icon, href, onClick } = props;\n\nconst Button = styled.button`\n display: flex;\n align-items: center;\n gap: 8px;\n\n border-radius: 4px;\n background: #04a46e;\n\n color: #f4f4f4;\n font-size: 16px;\n font-style: normal;\n font-weight: 700;\n line-height: 20px; /* 125% */\n\n padding: 0.5rem 1rem;\n\n &:hover {\n background: #555555;\n text-decoration: none !important;\n }\n\n outline: none;\n border: none;\n`;\n\nreturn (\n <div className={`d-flex flex-row-reverse ${props.className}`}>\n {props.href ? (\n <Link to={props.href} style={{ textDecoration: \"none\" }}>\n <Button\n className=\"post-control\"\n data-testid={props.testId ? props.testId : \"\"}\n >\n <i className={props.icon ? props.icon : \"bi bi-plus-circle-fill\"}></i>\n {props.title}\n </Button>\n </Link>\n ) : (\n <Button\n onClick={props.onClick}\n className=\"post-control\"\n data-testid={props.testId ? props.testId : \"\"}\n >\n <i className={props.icon ? props.icon : \"bi bi-plus-circle-fill\"}></i>\n {props.title || \"Post\"}\n </Button>\n )}\n </div>\n);\n" }, "devhub.entity.community.Provider": { "": "const { handle, Children } = props;\n\nconst {\n getAccountCommunityPermissions,\n createCommunity,\n updateCommunity,\n deleteCommunity,\n getCommunity,\n setCommunityAddons,\n} = VM.require(\"devhub.megha19.near/widget/core.adapter.devhub-contract\");\n\nif (\n !getCommunity ||\n !getAccountCommunityPermissions ||\n !createCommunity ||\n !updateCommunity ||\n !deleteCommunity ||\n !setCommunityAddons\n) {\n return <p>Loading modules...</p>;\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\nconst [isLoading, setIsLoading] = useState(false);\nconst [error, setError] = useState(null);\n\nconst community = getCommunity({ handle });\n\nconst permissions = getAccountCommunityPermissions({\n account_id: context.accountId,\n community_handle: handle,\n}) || {\n can_configure: false,\n can_delete: false,\n};\n\nif (isLoading) {\n return (\n <CenteredMessage height={\"384px\"}>\n <h2>Loading...</h2>\n </CenteredMessage>\n );\n} else if (!community) {\n return (\n <CenteredMessage height={\"384px\"}>\n <h2>{`Community with handle \"${handle}\" not found.`}</h2>\n </CenteredMessage>\n );\n}\n\nfunction handleUpdateCommunity(v) {\n updateCommunity(v);\n}\n\nreturn (\n <Children\n permissions={permissions}\n community={community}\n setCommunityAddons={setCommunityAddons}\n createCommunity={createCommunity}\n updateCommunity={handleUpdateCommunity}\n deleteCommunity={deleteCommunity}\n />\n);\n" }, "devhub.entity.proposal.CommentsAndLogs": { "": "const snapshotHistory = props.snapshotHistory;\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});\n\nfunction sortTimelineAndComments() {\n const comments = Social.index(\"comment\", props.item);\n\n if (state.changedKeysListWithValues === null) {\n const changedKeysListWithValues = snapshotHistory\n .slice(1)\n .map((item, index) => {\n const startingPoint = snapshotHistory[index]; // Set comparison to the previous item\n return {\n editorId: item.editor_id,\n ...getDifferentKeysWithValues(startingPoint, item),\n };\n });\n State.update({ changedKeysListWithValues });\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://near.social/devhub.near/widget/app?page=proposal&id=${props.id}&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.megha19.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: accountId,\n }}\n />\n </div>\n <CommentContainer\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={\n \"devhub.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"\n }\n props={{\n text: content.text,\n }}\n />\n\n <div className=\"d-flex gap-2 align-items-center mt-4\">\n <Widget\n src=\"devhub.megha19.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.megha19.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: oldValue,\n }}\n />\n to{\" \"}\n <Widget\n src={\"devhub.megha19.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 return !oldValue && newValue && <span>completed review</span>;\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 // 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.megha19.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 \"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\") {\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 className=\"inline-flex fw-bold text-black\">\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.page.admin.restrictedLabelsTab": { "": "const { accessControlInfo, createEditTeam, teamNames } = props;\n\nconst [createTeam, setCreateTeam] = useState(false);\n\nreturn (\n <>\n <h1>Restricted Labels</h1>\n <h5>\n Create special labels and control who can use and edit posts with those\n labels.\n </h5>\n {!createTeam ? (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.PostControls\"}\n props={{\n onClick: () => setCreateTeam(true),\n title: \"Create label\",\n testId: \"create-team\",\n }}\n />\n ) : (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.team.Configurator\"}\n props={{\n onCancel: () => setCreateTeam(false),\n onSubmit: (params) =>\n createEditTeam({ ...params, contractCall: \"add_member\" }),\n }}\n />\n )}\n\n <div class=\"table-responsive mt-3\">\n <table class=\"table table-sm table-bordered table-striped\">\n <thead class=\"thead-dark\">\n <tr>\n <th scope=\"col\">label name</th>\n <th scope=\"col\">Type</th>\n <th scope=\"col\">Members</th>\n <th scope=\"col\">Only allow members to use label</th>\n <th scope=\"col\">Allow members to edit any post with label</th>\n <th scope=\"col\">Actions</th>\n </tr>\n </thead>\n\n <tbody>\n {(teamNames || [])\n .filter(\n (teamName) =>\n teamName !== \"team:moderators\" && teamName.startsWith(\"team:\")\n )\n .sort()\n .map((teamName) => {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.team.LabelRow\"}\n props={{\n teamName,\n }}\n />\n );\n })}\n </tbody>\n </table>\n </div>\n </>\n);\n" }, "devhub.entity.community.configuration.AccessControlConfigurator": { "": "const CommunityAccessControlSchema = {\n admins: {\n format: \"comma-separated\",\n inputProps: { required: true },\n label: \"Admins\",\n order: 1,\n },\n};\n\nconst Struct = VM.require(\"devhub.megha19.near/widget/core.lib.struct\");\n\nif (!Struct) {\n return <p>Loading modules...</p>;\n}\n\nconst AutoComplete = styled.div`\n z-index: 5;\n\n > div > div {\n padding: calc(var(--padding) / 2);\n }\n`;\n\nconst Wrapper = styled.div`\n .container {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 0.5em;\n }\n\n .admins-item {\n display: inline-block;\n padding: 0.6em 0.8em;\n border-radius: 10px;\n border: 1px solid lightgray;\n position: relative;\n }\n\n .admins-item .remove {\n position: absolute;\n right: 5px;\n top: 0;\n font-size: 18px;\n color: grey;\n cursor: pointer;\n }\n\n .admins-input {\n flex-grow: 1;\n border: none;\n outline: none;\n }\n\n input[type=\"text\"]:disabled {\n all: inherit;\n }\n\n input::placeholder {\n font-size: 16px;\n }\n`;\n\nconst { data, onSubmit, onCancel, setIsActive, isActive } = props;\nconst initialValues = Struct.typeMatch(CommunityAccessControlSchema)\n ? Struct.pick(data ?? {}, Object.keys(CommunityAccessControlSchema))\n : {};\n\nconst [admins, setAdmins] = useState(initialValues?.admins ?? []);\nconst [text, setText] = useState(\"\");\nconst [showAccountAutocomplete, setShowAutoAutocomplete] = useState(false);\n\nfunction handleKeyDown(e) {\n if (e.key !== \"Enter\") return;\n const value = e.target.value;\n if (!value.trim()) return;\n // Add the value to the admins array\n setAdmins([...admins, value]);\n setText(\"\");\n}\n\nconst onCancelClick = () => {\n setAdmins(initialValues?.admins ?? []);\n setIsActive(false);\n};\n\nconst onSubmitClick = () => {\n onSubmit({ admins: admins.map((admin) => admin.trim()) });\n setIsActive(false);\n};\n\nfunction autoCompleteAccountId(id) {\n setAdmins([...admins, id]);\n setText(\"\");\n setShowAutoAutocomplete(false);\n}\n\nreturn (\n <Wrapper className=\"flex-grow-1 d-flex flex-column gap-4\">\n <div className=\"container\">\n {admins.map((admin, index) => (\n <div className=\"admins-item\" key={index}>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.ProfileCard\"}\n props={{\n accountId: admin,\n nearDevGovGigsWidgetsAccountId: \"devhub.megha19.near\",\n openLinkInNewTab: true,\n }}\n />\n {/* don't allow removal if only 1 admin is added */}\n {admins.length > 1 && isActive && (\n <span\n className=\"remove\"\n onClick={() => setAdmins(admins.filter((item) => item !== admin))}\n >\n &times;\n </span>\n )}\n </div>\n ))}\n <input\n disabled={!isActive}\n value={text}\n onChange={(v) => {\n setShowAutoAutocomplete(true);\n setText(v.target.value);\n }}\n onKeyDown={handleKeyDown}\n type=\"text\"\n className=\"admins-input\"\n placeholder={isActive && \"Add Admins here...\"}\n />\n </div>\n {showAccountAutocomplete && (\n <AutoComplete>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.AccountAutocomplete\"\n props={{\n term: text,\n onSelect: autoCompleteAccountId,\n onClose: () => setShowAutoAutocomplete(false),\n filterAccounts: admins,\n }}\n />\n </AutoComplete>\n )}\n {isActive && (\n <div className=\"d-flex align-items-center justify-content-end gap-3 mt-auto\">\n <Widget\n src={\"devhub.megha19.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.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-success\" },\n disabled: Struct.isEqual(admins, initialValues?.admins ?? []),\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Submit\",\n onClick: onSubmitClick,\n }}\n />\n </div>\n )}\n </Wrapper>\n);\n" }, "devhub.entity.addon.blog.editor.provider": { "": "const { getPost } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n) || { getPost: () => {} };\n\nconst { Layout, handle } = props;\n\nconst QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql/`;\n\nconst fetchGraphQL = (operationsDoc, operationName, variables) => {\n return fetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `bo_near` },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n};\n\nconst queryName =\n props.queryName ?? `bo_near_devhub_v38_posts_with_latest_snapshot`;\n\nconst query = `query DevhubPostsQuery($limit: Int = 100, $offset: Int = 0, $where: ${queryName}_bool_exp = {}) {\n ${queryName}(\n limit: $limit\n offset: $offset\n order_by: {block_height: desc}\n where: $where\n ) {\n post_id\n }\n }\n`;\n\nconst includeLabels = [\"blog\", handle];\n\nconst buildWhereClause = () => {\n let where = {};\n if (props.author) {\n where = { author_id: { _eq: props.author }, ...where };\n }\n if (props.term) {\n where = { description: { _ilike: `%${props.term}%` }, ...where };\n }\n if (includeLabels && Array.isArray(includeLabels)) {\n const labelConditions = includeLabels.map((label) => ({\n labels: { _contains: label },\n }));\n\n where = { _and: [...labelConditions, where] };\n }\n if (props.excludeLabels && Array.isArray(props.excludeLabels)) {\n const labelConditions = props.excludeLabels.map((label) => ({\n labels: { _nin: label },\n }));\n\n where = { _and: [...labelConditions, where] };\n }\n if (!props.recency) {\n where = { parent_id: { _is_null: true }, ...where };\n }\n return where;\n};\n\nconst variables = { limit: DISPLAY_COUNT, offset, where: buildWhereClause() };\n\nconst posts = fetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `bo_near` },\n body: JSON.stringify({\n query: query,\n variables: variables,\n operationName: \"DevhubPostsQuery\",\n }),\n});\n\nconst handleOnChange = (v) => {\n console.log(\"onChange\", v);\n};\n\nconst handleGetData = (v) => {\n const postId = parseInt(v);\n return Near.asyncView(\"devgovgigs.near\", \"get_post\", {\n post_id: postId,\n }).then((post) => {\n const description = JSON.parse(post.snapshot.description || \"null\") || {};\n return {\n id: postId,\n ...description,\n };\n });\n};\n\nconst handleOnSubmit = (v, isEdit) => {\n if (isEdit) {\n Near.call({\n contractName: \"devgovgigs.near\",\n methodName: \"edit_post\",\n args: {\n id: parseInt(v.id),\n labels: [\"blog\", handle],\n body: {\n post_type: \"Comment\",\n description: JSON.stringify(v),\n comment_version: \"V2\",\n },\n },\n gas: Big(10).pow(14),\n });\n } else {\n Near.call({\n contractName: \"devgovgigs.near\",\n methodName: \"add_post\",\n args: {\n labels: [\"blog\", handle],\n body: {\n post_type: \"Comment\",\n description: JSON.stringify(v),\n comment_version: \"V2\",\n },\n },\n gas: Big(10).pow(14),\n });\n }\n};\n\nconst handleOnCancel = (v) => {\n console.log(\"onCancel\", v);\n};\n\nreturn (\n <Layout\n data={posts.body.data.bo_near_devhub_v38_posts_with_latest_snapshot || []}\n getData={handleGetData}\n onChange={handleOnChange}\n onSubmit={handleOnSubmit}\n onCancel={handleOnCancel}\n />\n);\n" }, "devhub.entity.addon.wiki.Configurator": { "": "const { data, onSubmit } = props;\n\nconst initialData = data;\nconst [content, setContent] = useState(data.content || \"\");\nconst [title, setTitle] = useState(data.title || \"\");\nconst [subtitle, setSubtitle] = useState(data.subtitle || \"\");\n\nconst [textAlign, setTextAlign] = useState(data.textAlign || \"left\");\n\nconst Container = styled.div`\n width: 100%;\n margin: 0 auto;\n padding: 20px;\n text-align: left;\n`;\n\nconst FormContainer = styled.div`\n padding-top: 1rem;\n\n & > *:not(:last-child) {\n margin-bottom: 1rem;\n }\n`;\n\nconst hasDataChanged = () => {\n return (\n content !== initialData.content ||\n title !== initialData.title ||\n subtitle !== initialData.subtitle ||\n textAlign !== initialData.textAlign\n );\n};\n\nconst handleSubmit = () => {\n if (title) onSubmit({ title, subtitle, content, textAlign });\n};\n\nreturn (\n <Container>\n <ul className=\"nav nav-tabs\" id=\"editPreviewTabs\" role=\"tablist\">\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className=\"nav-link active\"\n id=\"edit-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#edit\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"edit\"\n aria-selected=\"true\"\n >\n Edit\n </button>\n </li>\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className=\"nav-link\"\n id=\"preview-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#preview\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"preview\"\n aria-selected=\"false\"\n >\n Preview\n </button>\n </li>\n </ul>\n <div className=\"tab-content\" id=\"editPreviewTabsContent\">\n <div\n className=\"tab-pane show active p-4\"\n id=\"edit\"\n role=\"tabpanel\"\n aria-labelledby=\"edit-tab\"\n style={{ position: \"relative\" }}\n >\n <div style={{ position: \"absolute\", top: 10, right: 0 }}>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Switch\"\n props={{\n currentValue: textAlign,\n key: \"textAlign\",\n onChange: (e) => setTextAlign(e.target.value),\n options: [\n { label: \"Left\", value: \"left\" },\n { label: \"Center\", value: \"center\" },\n { label: \"Right\", value: \"right\" },\n ],\n }}\n />\n </div>\n <FormContainer>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n label: \"Title\",\n className: \"flex-grow-1\",\n onChange: (e) => setTitle(e.target.value),\n value: title,\n inputProps: {\n min: 2,\n max: 60,\n required: true,\n },\n }}\n />\n </div>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n label: \"Subtitle\",\n className: \"flex-grow-1\",\n onChange: (e) => setSubtitle(e.target.value),\n value: subtitle,\n inputProps: {\n min: 2,\n max: 250,\n },\n }}\n />\n </div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.MarkdownEditor\"\n props={{ data: { content }, onChange: setContent }}\n />\n </FormContainer>\n <div\n className={\"d-flex align-items-center justify-content-end gap-3 mt-4\"}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-success\" },\n disabled: !hasDataChanged() || !title || !content,\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Publish\",\n onClick: handleSubmit,\n }}\n />\n </div>\n </div>\n <div\n className=\"tab-pane\"\n id=\"preview\"\n role=\"tabpanel\"\n aria-labelledby=\"preview-tab\"\n >\n <div className=\"w-100 h-100 p-4\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.addon.wiki.Viewer\"\n props={{ title, subtitle, content, textAlign }}\n />\n </div>\n </div>\n </div>\n </Container>\n);\n" }, "devhub.feature.proposal-search.by-stage": { "": "// The timeline is stored in jsonb. The value is used to search for as part of\n// that json so it doesn't need to be an exact match.\nconst options = [\n { label: \"Draft\", value: \"DRAFT\" },\n { label: \"Review\", value: \"REVIEW\" },\n { label: \"Approved\", value: \"APPROVED\" },\n { label: \"Approved - Conditional\", value: \"CONDITIONAL\" },\n { label: \"Rejected\", value: \"REJECTED\" },\n { label: \"Cancelled\", value: \"CANCELLED\" },\n { label: \"Payment Processing\", value: \"PAYMENT\" },\n { label: \"Funded\", value: \"FUNDED\" },\n { label: \"None\", value: \"\" },\n];\n\nconst setSelected = props.onStateChange ?? (() => {});\n\nreturn (\n <div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: options,\n label: \"Stage\",\n onUpdate: (v) => {\n setSelected(v);\n },\n }}\n />\n </div>\n);\n" }, "devhub.notification.LR": { "": "let { accountId, blockHeight, value } = props;\n\nconst showAuthorProfile =\n value.type == \"devhub/mention\" || value.type == \"devhub/edit\";\n\nif (showAuthorProfile) {\n accountId = value.notifier;\n}\n\nreturn (\n <div className=\"d-flex justify-content-between\">\n <div className=\"me-2 text-truncate\">\n <div className=\"text-truncate\">\n <Widget\n src=\"mob.near/widget/N.ProfileLine\"\n props={{ accountId, tooltip: true, link: true }}\n />\n </div>\n <div className=\"text-truncate text-muted\" style={{ paddingLeft: \"1em\" }}>\n {props.L}\n <Widget src=\"mob.near/widget/TimeAgo\" props={{ blockHeight }} />\n </div>\n </div>\n <div className=\"text-nowrap\">{props.R}</div>\n </div>\n);\n" }, "devhub.entity.community.Sidebar": { "": "const { href } = VM.require(\"devhub.megha19.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.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"}\n props={{\n text: community.bio_markdown,\n }}\n />\n <small class=\"text-muted mb-3\">\n <Link\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"feed\", tag: community.tag },\n })}\n >\n <Widget\n src={\"devhub.megha19.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.megha19.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.megha19.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.megha19.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.entity.community.configuration.ConfigurationSection": { "": "const {\n title,\n hasConfigurePermissions,\n Configurator,\n Preview,\n headerRight,\n forceEditActive,\n} = props;\n\nconst [isEditActive, setEditActive] = useState(forceEditActive || false);\n\nfunction SectionHeader() {\n return (\n <div\n className=\"d-flex align-items-center justify-content-between w-100 pb-3\"\n style={{ minHeight: 30 }}\n >\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n <span>{title}</span>\n </h5>\n {headerRight ||\n (hasConfigurePermissions && (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-sm btn-secondary\" },\n icon: {\n type: \"bootstrap_icon\",\n variant: isEditActive ? \"bi-x-circle\" : \"bi-pen-fill\",\n },\n label: isEditActive ? \"Cancel\" : \"Edit\",\n onClick: () => setEditActive(!isEditActive),\n }}\n />\n ))}\n </div>\n );\n}\n\nreturn (\n <div>\n <SectionHeader />\n <Configurator\n isActive={isEditActive}\n setIsActive={setEditActive}\n onCancel={() => setEditActive(!isEditActive)}\n />\n </div>\n);\n" }, "devhub.entity.proposal.AccountInput": { "": "const value = props.value;\nconst placeholder = props.placeholder;\nconst onUpdate = props.onUpdate;\n\nconst [account, setAccount] = useState(value);\nconst [showAccountAutocomplete, setAutoComplete] = useState(false);\nconst [isValidAccount, setValidAccount] = useState(true);\nconst AutoComplete = styled.div`\n max-width: 400px;\n margin-top: 1rem;\n`;\n\nuseEffect(() => {\n if (value !== account) {\n setAccount(value);\n }\n}, [value]);\n\nuseEffect(() => {\n if (value !== account) {\n onUpdate(account);\n }\n}, [account]);\n\nuseEffect(() => {\n const handler = setTimeout(() => {\n const valid = account.length === 64 || (account ?? \"\").includes(\".near\");\n setValidAccount(valid);\n setAutoComplete(!valid);\n }, 100);\n\n return () => {\n clearTimeout(handler);\n };\n}, [account]);\n\nreturn (\n <div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: account,\n onChange: (e) => {\n setAccount(e.target.value);\n },\n skipPaddingGap: true,\n placeholder: placeholder,\n inputProps: {\n max: 64,\n prefix: \"@\",\n },\n }}\n />\n {account && !isValidAccount && (\n <div style={{ color: \"red\" }} className=\"text-sm mt-1\">\n Please enter valid account ID\n </div>\n )}\n {showAccountAutocomplete && (\n <AutoComplete>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.AccountAutocomplete\"\n props={{\n term: account,\n onSelect: (id) => {\n setAccount(id);\n setAutoComplete(false);\n },\n onClose: () => setAutoComplete(false),\n }}\n />\n </AutoComplete>\n )}\n </div>\n);\n" }, "devhub.feature.proposal-search.by-category": { "": "const options = [\n {\n label: \"DevDAO Operations\",\n value: \"DevDAO Operations\",\n },\n {\n label: \"DevDAO Platform\",\n value: \"DevDAO Platform\",\n },\n {\n label: \"Events & Hackathons\",\n value: \"Events & Hackathons\",\n },\n {\n label: \"Engagement & Awareness\",\n value: \"Engagement & Awareness\",\n },\n {\n label: \"Decentralized DevRel\",\n value: \"Decentralized DevRel\",\n },\n {\n label: \"Universities & Bootcamps\",\n value: \"Universities & Bootcamps\",\n },\n {\n label: \"Tooling & Infrastructure\",\n value: \"Tooling & Infrastructure\",\n },\n {\n label: \"Other\",\n value: \"Other\",\n },\n {\n label: \"None\",\n value: \"\",\n },\n];\n\nconst setSelected = props.onStateChange ?? (() => {});\n\nreturn (\n <div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: options,\n label: \"Category\",\n onUpdate: (v) => {\n setSelected(v);\n },\n }}\n />\n </div>\n);\n" }, "devhub.components.molecule.Select": { "": "const SelectInput = ({\n className,\n key,\n label,\n onChange,\n options,\n placeholder,\n value,\n style,\n ...otherProps\n}) => {\n const renderedLabels =\n [(label?.length ?? 0) > 0 && <span>{label}</span>] || [];\n\n return (\n <div\n className={[\n \"d-flex flex-column flex-1 align-items-start justify-content-evenly\",\n className,\n ].join(\" \")}\n style={style}\n {...otherProps}\n >\n {renderedLabels.length > 0 && (\n <span className=\"d-flex justify-content-between align-items-center gap-3 w-100\">\n {renderedLabels}\n </span>\n )}\n\n <div className=\"input-group\">\n <select\n className=\"form-select border border-2\"\n value={value}\n onChange={onChange}\n aria-label={label}\n >\n <option value=\"\" disabled hidden>\n {placeholder}\n </option>\n {options.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n );\n};\n\nreturn SelectInput(props);\n" }, "devhub.components.layout.LikeButton.Faces": { "": "const accountId = context.accountId;\nconst likesByUsers = props.likesByUsers || {};\nconst limit = props.limit ?? 3;\n\nlet likes = Object.keys(likesByUsers).reverse();\n\nconst graphLikes = [];\nconst nonGraph = [];\n\nconst graph =\n (accountId &&\n Social.keys(`${accountId}/graph/follow/*`, \"final\")[accountId].graph\n .follow) ||\n {};\n\nlikes.forEach((accountId) => {\n if (accountId in graph) {\n graphLikes.push(accountId);\n } else {\n nonGraph.push(accountId);\n }\n});\n\nlet faces = [...graphLikes, ...nonGraph];\n\nif (faces.length < limit + 3) {\n limit = faces.length;\n}\n\nconst renderFaces = faces.splice(0, limit);\n\nconst Faces = styled.span`\n .face {\n display: inline-block;\n position: relative;\n margin: -0.1em;\n height: 1.5em;\n width: 1.5em;\n min-width: 1.5em;\n vertical-align: top;\n img {\n object-fit: cover;\n border-radius: 50%;\n width: 100%;\n height: 100%;\n }\n }\n`;\n\nconst Others = styled.span`\n &:hover {\n color: white !important;\n }\n`;\n\nconst numLikes = likes.length - limit;\n\nreturn (\n <>\n <Faces className=\"ms-1\">\n {renderFaces.map((accountId, i) => (\n <a\n key={i}\n href={`#/mob.near/widget/ProfilePage?accountId=${accountId}`}\n className=\"text-decoration-none d-inline-block\"\n >\n <Widget\n src=\"mob.near/widget/Profile.OverlayTrigger\"\n props={{\n accountId,\n children: (\n <Widget\n src=\"mob.near/widget/ProfileImage\"\n props={{\n metadata,\n accountId,\n widgetName,\n style: { zIndex: 10 - i },\n className: \"face\",\n tooltip: false,\n imageStyle: {},\n imageClassName: \"\",\n }}\n />\n ),\n }}\n />\n </a>\n ))}\n </Faces>\n {numLikes > 0 ? (\n <OverlayTrigger\n placement=\"auto\"\n overlay={\n <Tooltip>\n <div\n className=\"text-truncate text-start\"\n style={{ maxWidth: \"16em\" }}\n >\n {faces.slice(0, 10).map((accountId, i) => (\n <Fragment key={i}>\n <Widget\n src=\"mob.near/widget/ProfileLine\"\n props={{ accountId, link: false }}\n />\n <br />\n </Fragment>\n ))}\n {faces.length > 10 ? \"...\" : \"\"}\n </div>\n </Tooltip>\n }\n >\n <span className=\"ms-1\">\n and {numLikes} other{numLikes === 1 ? \"\" : \"s\"}\n </span>\n </OverlayTrigger>\n ) : (\n \"\"\n )}\n </>\n);\n" }, "devhub.components.molecule.Checkbox": { "": "const CheckBox = ({ value, isChecked, label, onClick }) => {\n const [checked, setChecked] = useState(isChecked);\n\n useEffect(() => {\n if (isChecked !== checked) {\n setChecked(isChecked);\n }\n }, [isChecked]);\n\n useEffect(() => {\n onClick(checked);\n }, [checked]);\n\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n value={value}\n checked={checked}\n onChange={(e) => setChecked(e.target.checked)}\n />\n <label class=\"form-check-label text-sm\">{label}</label>\n </div>\n );\n};\n\nreturn CheckBox(props);\n" }, "devhub.entity.community.Activity": { "": "const { handle } = props;\n\nconst { getCommunity } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\ngetCommunity = getCommunity || (() => <></>);\nhref || (href = () => {});\n\nif (!handle) {\n return <p>Handle not defined</p>;\n}\n\nif (!href) {\n return <p>Loading modules...</p>;\n}\n\n// TODO: Why do we need to get community data again? Isn't the tag the handle...\nconst communityData = getCommunity({ handle });\n\nif (communityData === null) {\n return <div>Loading...</div>;\n}\n\nconst MainContent = styled.div`\n flex-grow: 1;\n max-width: 75%;\n\n @media screen and (max-width: 960px) {\n max-width: 100%;\n }\n`;\n\nconst SidebarContainer = styled.div`\n max-width: 25%;\n margin-right: 1.5rem;\n\n @media screen and (max-width: 960px) {\n display: none;\n }\n`;\n\nreturn (\n <div style={{ maxWidth: \"100%\", width: \"100%\" }}>\n <div class=\"col\">\n <div class=\"d-flex w-100\">\n <MainContent>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.feature.post-search.panel\"}\n props={{\n hideHeader: true,\n tag: communityData.tag,\n children: (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.components.molecule.PostControls\"\n }\n props={{\n title: \"Post\",\n href: href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"create\",\n labels: [communityData.tag],\n },\n }),\n }}\n />\n ),\n recency,\n transactionHashes: props.transactionHashes,\n }}\n />\n </MainContent>\n <SidebarContainer>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.community.Sidebar\"}\n props={{ community: communityData }}\n />\n </SidebarContainer>\n </div>\n </div>\n </div>\n);\n" }, "devhub.entity.proposal.CategoryTag": { "": "const category = props.category;\nconst getClassNameByCategory = () => {\n switch (category) {\n case \"DevDAO Operations\":\n return \"purple\";\n case \"DevDAO Platform\":\n return \"yellow\";\n case \"Decentralized DevRel\":\n return \"cyan\";\n case \"Universities & Bootcamps\":\n return \"mustard\";\n case \"Engagement & Awareness\":\n return \"red\";\n case \"Events & Hackathons\":\n return \"green\";\n case \"Tooling & Infrastructure\":\n return \"pink\";\n default:\n return \"grey\";\n }\n};\n\nconst Container = styled.div`\n @media screen and (max-width: 768px) {\n font-size: 11px;\n }\n font-size: 13px;\n .tag {\n color: white;\n padding-inline: 6px;\n padding-block: 3px;\n }\n .purple-bg {\n background-color: #7c66dc;\n }\n .yellow-bg {\n background-color: #dcc266;\n }\n .cyan-bg {\n background-color: #0daebb;\n }\n .pink-bg {\n background-color: #d366dc;\n }\n .grey-bg {\n background-color: #818181;\n }\n .red-bg {\n background-color: #dc6666;\n }\n .green-bg {\n background-color: #04a46e;\n }\n .mustard-bg {\n background-color: #dc9866;\n }\n`;\n\nreturn (\n <Container>\n <div className={getClassNameByCategory() + \"-bg rounded-1 tag\"}>\n {category}\n </div>\n </Container>\n);\n" }, "devhub.entity.proposal.CategoryDropdown": { "": "const { selectedValue, onChange, disabled } = props;\n\nonChange = onChange || (() => {});\n\nconst options = [\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreiet5w62oeef6msfsakdskq7zkjk33ngogcerfdmqewnsuj74u376e\",\n title: \"DevDAO Operations\",\n description:\n \"Provide core operations and leadership for the DAO or infrastructure support.\",\n value: \"DevDAO Operations\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreiengkdru4fczwltjylfqeeypsdf4hb5fdxa6t67l3x2qtqgeo3pzq\",\n title: \"DevDAO Platform\",\n description:\n \"Build & maintain the interface for DevHub’s community & funding activities.\",\n value: \"DevDAO Platform\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreicpt3ulwsmptzdbtkhvxodvo7pcajcpyr35tqcbfdnaipzrx5re7e\",\n title: \"Events & Hackathons\",\n description:\n \"Organize or support events, hackathons, and local meet ups to grow communities.\",\n value: \"Events & Hackathons\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreibdrwhbouuutvrk4qt2udf4kumbyy5ebjkezobbahxvo7fyxo2ec4\",\n title: \"Engagement & Awareness\",\n description:\n \"Create content from social posts to real world swag to drive awareness to NEAR.\",\n value: \"Engagement & Awareness\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreiem2vjsp6wu3lkd4zagpm43f32egdjjzchmleky6rr2ydzhlkrxam\",\n title: \"Decentralized DevRel\",\n description:\n \"Provide support, gather feedback, and maintain docs to drive engagement.\",\n value: \"Decentralized DevRel\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreic3prsy52hwueugqj5rwualib4imguelezsbvgrxtezw4u33ldxqq\",\n title: \"Universities & Bootcamps\",\n description:\n \"Engage with students and universities globally to encourage NEAR.\",\n value: \"Universities & Bootcamps\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreigf7j5isssumbjl24zy4pr27ryfqivan3vuwu2uwsofcujhhkk7cq\",\n title: \"Tooling & Infrastructure\",\n description:\n \"Contribute code to NEAR tooling or facilitating technical decisions.\",\n value: \"Tooling & Infrastructure\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreihctatkwnvpmblgqnpw76zggfet3fmpgurqvtj7vbm3cb5r3pp52u\",\n title: \"Other\",\n description: \"Use this category if you are not sure which one to use.\",\n value: \"Other\",\n },\n];\n\nconst [isOpen, setIsOpen] = useState(false);\nconst [selectedOptionValue, setSelectedValue] = useState(selectedValue);\n\nconst toggleDropdown = () => {\n setIsOpen(!isOpen);\n};\n\nuseEffect(() => {\n if (selectedValue && selectedValue !== selectedOptionValue) {\n setSelectedValue(selectedValue);\n }\n}, [selectedValue]);\n\nuseEffect(() => {\n if (selectedValue !== selectedOptionValue) {\n onChange(selectedOptionValue);\n }\n}, [selectedOptionValue]);\n\nconst handleOptionClick = (option) => {\n setSelectedValue(option.value);\n setIsOpen(false);\n};\n\nconst Container = styled.div`\n .drop-btn {\n width: 100%;\n text-align: left;\n padding-inline: 10px;\n }\n\n .dropdown-toggle:after {\n position: absolute;\n top: 46%;\n right: 2%;\n }\n\n .dropdown-menu {\n width: 100%;\n }\n\n .dropdown-item.active,\n .dropdown-item:active {\n background-color: #f0f0f0 !important;\n color: black;\n }\n\n .disabled {\n background-color: #f8f8f8 !important;\n cursor: not-allowed !important;\n border-radius: 5px;\n opacity: inherit !important;\n }\n\n .disabled.dropdown-toggle::after {\n display: none !important;\n }\n\n .custom-select {\n position: relative;\n }\n\n .selected {\n background-color: #f0f0f0;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n`;\n\nconst Item = ({ option }) => {\n if (!option) {\n return <div className=\"text-muted\">Select Category</div>;\n }\n return (\n <div className=\"d-flex gap-3 align-items-center w-100\">\n <img src={option.icon} height={30} />\n <div className=\"d-flex flex-column gap-1 w-100 text-wrap\">\n <div className=\"h6 mb-0\"> {option.title}</div>\n <div className=\"text-sm text-muted w-100 text-wrap\">\n {option.description}\n </div>\n </div>\n </div>\n );\n};\n\nconst selectedOption =\n options.find((item) => item.value === selectedOptionValue) ?? null;\n\nreturn (\n <Container>\n <div\n className=\"custom-select w-100\"\n tabIndex=\"0\"\n onBlur={() => setIsOpen(false)}\n >\n <div\n className={\n \"dropdown-toggle bg-white border rounded-2 btn drop-btn w-100 \" +\n (disabled ? \"disabled\" : \"\")\n }\n onClick={!disabled && toggleDropdown}\n >\n <div className={`selected-option`}>\n <Item option={selectedOption} />\n </div>\n </div>\n\n {isOpen && (\n <div className=\"dropdown-menu rounded-2 dropdown-menu-end dropdown-menu-lg-start px-2 shadow show w-100\">\n <div>\n {options.map((option) => (\n <div\n key={option.value}\n className={`dropdown-item cursor-pointer w-100 my-1 ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n onClick={() => handleOptionClick(option)}\n >\n <Item option={option} />\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n </Container>\n);\n" }, "core.lib.url": { "": "/**\n * Generates a URL to a widget.\n *\n * @param {Object} options - Configuration options for constructing the URL.\n * @param {string} [options.gateway] - The gateway or server address where the widget source is hosted (optional).\n * @param {string} options.widgetSrc - The source path of the widget (required).\n * @param {Object} [options.params] - An object containing key-value pairs representing query parameters to be appended to the URL (optional).\n * @returns {string} - The constructed URL.\n */\nfunction href({ gateway, widgetSrc, params }) {\n // Check if query parameters are provided and filter out null values\n if (params) {\n params = (Object.entries(params) || [])\n .filter(([_key, nullable]) => (nullable ?? null) !== null)\n .map(([key, value]) => {\n // Omit the parameter if the value is null or the array is empty\n if (value === null || (Array.isArray(value) && value.length === 0)) {\n return null;\n }\n\n // Convert array values to a comma-separated string with no spaces\n if (Array.isArray(value)) {\n return `${key}=${value.join(\",\")}`;\n } else {\n return `${key}=${value}`;\n }\n })\n .join(\"&\");\n }\n\n // Check if the gateway already includes \"https://\" and construct the final URL accordingly\n if (gateway) {\n if (/(^https:\\/\\/)|(^http:\\/\\/)/.test(gateway)) {\n return `/${gateway}/${widgetSrc}${params && `?${params}`}`;\n } else {\n return `https://${gateway}/${widgetSrc}${params && `?${params}`}`;\n }\n } else {\n return `/${widgetSrc}${params && `?${params}`}`;\n }\n}\n\nreturn { href };\n" }, "devhub.entity.addon.blog.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} = props;\n\nconst TitleInput = ({ title, setTitle }) => {\n return (\n <div>\n <h5>Title</h5>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n onChange: (e) => setTitle(e.target.value),\n value: title,\n placeholder: \"Title\",\n inputProps: { name: \"title\" },\n }}\n />\n </div>\n </div>\n );\n};\nconst SubtitleInput = ({ subtitle, setSubtitle }) => {\n return (\n <div>\n <h5>Subtitle</h5>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n onChange: (e) => setSubtitle(e.target.value),\n value: subtitle,\n placeholder: \"Subtitle\",\n inputProps: { name: \"subtitle\" },\n }}\n />\n </div>\n </div>\n );\n};\n\nconst CategorySelect = ({ options, category, setCategory }) => {\n return (\n <div>\n <h5>Category</h5>\n <div className=\"flex-grow-1\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Select\"}\n props={{\n className: \"flex-grow-1\",\n options,\n value: category,\n onChange: (e) => setCategory(e.target.value),\n placeholder: \"Select a category\",\n }}\n />\n </div>\n </div>\n );\n};\n\nconst DescriptionInput = ({ description, setDescription }) => {\n return (\n <div>\n <h5>Description</h5>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n onChange: (e) => setDescription(e.target.value),\n value: description,\n placeholder: \"Description\",\n inputProps: { name: \"description\" },\n }}\n />\n </div>\n </div>\n );\n};\n\nconst AuthorInput = ({ author, setAuthor }) => {\n return (\n <div>\n <h5>Author</h5>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.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: { name: \"author\" },\n }}\n />\n </div>\n </div>\n );\n};\n\nconst DateInput = ({ date, setDate }) => {\n return (\n <div>\n <h5>Date</h5>\n <input\n name=\"date\"\n type=\"date\"\n value={date}\n onChange={(e) => setDate(e.target.value)}\n />\n </div>\n );\n};\n\nconst ContentEditor = ({ content, setContent }) => {\n return (\n <div>\n <h5>Content</h5>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.MarkdownEditor\"\n props={{ data: { content }, onChange: setContent }}\n />\n </div>\n );\n};\n\nreturn (\n <FormContainer id=\"blog-editor-form\">\n <TitleInput title={title} setTitle={setTitle} />\n <SubtitleInput subtitle={subtitle} setSubtitle={setSubtitle} />\n <CategorySelect\n options={options}\n category={category}\n setCategory={setCategory}\n />\n <DescriptionInput\n description={description}\n setDescription={setDescription}\n debouncedUpdateState={debouncedUpdateState}\n />\n <AuthorInput author={author} setAuthor={setAuthor} />\n <DateInput date={date} setDate={setDate} />\n <ContentEditor content={content} setContent={setContent} />\n </FormContainer>\n);\n" }, "core.lib.common": { "": "// https://docs.rs/near-sdk/latest/near_sdk/env/constant.STORAGE_PRICE_PER_BYTE.html\nconst STORAGE_PRICE_PER_BYTE = \"10000000000000000000\";\n// https://github.com/NearSocial/social-db/blob/d28c647252ce25a06c70c3b7f4930ccdcd217fd9/contract/src/account.rs#L8C5-L8C50\nconst MIN_STORAGE_BYTES = \"2000\";\nconst MIN_STORAGE_COST = Big(STORAGE_PRICE_PER_BYTE).times(MIN_STORAGE_BYTES);\n\n// in case the user is new and doesn't have min storage cost, increasing the deposit\nfunction getDepositAmountForWriteAccess(userStorageDeposit) {\n const depositAmt = Big(userStorageDeposit?.available).gt(MIN_STORAGE_COST)\n ? Big(10).pow(22)\n : Big(MIN_STORAGE_COST).plus(Big(10).pow(22));\n\n return depositAmt;\n}\n\nfunction readableDate(timestamp) {\n var a = new Date(timestamp);\n var options = {\n month: \"short\",\n day: \"2-digit\",\n year: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n timeZone: \"UTC\",\n };\n return a.toLocaleString(\"en-US\", options) + \" UTC\";\n}\n\nreturn { getDepositAmountForWriteAccess, readableDate };\n" }, "devhub.entity.addon.blog.Viewer": { "": "const { Card } =\n VM.require(\"devhub.megha19.near/widget/devhub.entity.addon.blog.Card\") ||\n (() => <></>);\n\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nconst { includeLabels, excludeLabels, layout, handle, hideTitle } = props;\n\nconst Grid = styled.div`\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 1rem;\n\n @media screen and (max-width: 768px) {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n }\n`;\n\nconst Heading = styled.h3`\n color: #151515;\n font-size: 2rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 48px */\n margin-bottom: 2rem;\n\n @media screen and (max-width: 768px) {\n font-size: 1.5rem;\n }\n`;\n\nconst CardContainer = styled.div`\n transition: all 300ms;\n border-radius: 1rem;\n height: 100%;\n\n &:hover {\n box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1),\n 0 4px 6px -4px rgb(0 0 0 / 0.1);\n }\n`;\n\nfunction BlogCard(postId) {\n return (\n <Link\n style={{ textDecoration: \"none\" }}\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"blog\", id: postId },\n })}\n >\n <CardContainer>\n <Widget // We need this so the individual posts can make the necessary call for more data\n src=\"devhub.megha19.near/widget/devhub.entity.post.Postv2\"\n props={{ postKey: postId, template: (p) => <Card {...(p || {})} /> }} // I wonder if this could take list of types, their templates, normalizer functions, etc... and have this all as a module\n />\n {/* // so then you could swap between devhub contract or social contract sources, it doesn't matter. */}\n </CardContainer>\n </Link>\n );\n}\n\nreturn (\n <div class=\"w-100\">\n {!hideTitle && <Heading>Latest Blog Posts</Heading>}\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.addon.blog.Feed\"}\n // TODO: This needs to filter by more labels\n props={{\n includeLabels: [\"blog\", handle, ...(includeLabels || [])], // make sure this has the community handle\n excludeLabels: excludeLabels || [],\n renderItem: BlogCard,\n Layout: ({ children }) => <Grid>{children}</Grid>,\n }}\n />\n </div>\n);\n" }, "devhub.components.organism.Feed.NearQueryApi": { "": "const LIMIT = 10;\nconst filteredAccountIds = props.filteredAccountIds;\nconst setPostExists = props.setPostExists ?? (() => {});\nconst GRAPHQL_ENDPOINT =\n props.GRAPHQL_ENDPOINT ?? \"https://near-queryapi.api.pagoda.co\";\n\nconst sort = props.sort || \"desc\";\n\n// get the full list of posts that the current user has flagged so\n// they can be hidden\nconst selfFlaggedPosts = context.accountId\n ? Social.index(\"flag\", \"main\", {\n accountId: context.accountId,\n }) ?? []\n : [];\n\n// V2 self moderation data, structure is like:\n// { moderate: {\n// \"account1.near\": \"report\",\n// \"account2.near\": {\n// \".post.main\": { // slashes are not allowed in keys\n// \"100000123\": \"spam\", // post ids are account/blockHeight\n// }\n// },\n// }\n// }\nconst selfModeration = context.accountId\n ? Social.getr(`${context.accountId}/moderate`, \"optimistic\") ?? []\n : [];\nconst postsModerationKey = \".post.main\";\nconst commentsModerationKey = \".post.comment\";\nconst matchesModeration = (moderated, socialDBObjectType, item) => {\n if (!moderated) return false;\n const accountFound = moderated[item.account_id];\n if (typeof accountFound === \"undefined\") {\n return false;\n }\n if (typeof accountFound === \"string\" || accountFound[\"\"]) {\n return true;\n }\n const moderatedItemsOfType = accountFound[socialDBObjectType];\n return (\n moderatedItemsOfType &&\n typeof moderatedItemsOfType[item.block_height] !== \"undefined\"\n );\n};\n\nconst shouldFilter = (item, socialDBObjectType) => {\n return (\n selfFlaggedPosts.find((flagged) => {\n return (\n flagged?.value?.blockHeight === item.block_height &&\n flagged?.value?.path.includes(item.account_id)\n );\n }) || matchesModeration(selfModeration, socialDBObjectType, item)\n );\n};\nfunction fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(`${GRAPHQL_ENDPOINT}/v1/graphql`, {\n method: \"POST\",\n headers: { \"x-hasura-role\": \"dataplatform_near\" },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n}\n\nconst createQuery = (type, isUpdate) => {\n let querySortOption = \"\";\n switch (sort) {\n case \"recentcommentdesc\":\n querySortOption = `{ last_comment_timestamp: desc_nulls_last },`;\n break;\n default:\n querySortOption = \"\";\n }\n\n let queryFilter = \"\";\n let timeOperation = \"_lte\";\n if (isUpdate) {\n timeOperation = \"_gt\";\n }\n\n const queryTime = initialQueryTime ? initialQueryTime : Date.now() * 1000000;\n\n if (filteredAccountIds) {\n queryFilter = `where: {\n _and: [\n {account_id: {_in: \"${filteredAccountIds}\"}},\n {block_timestamp: {${timeOperation}: ${queryTime}}}\n ]\n }, `;\n } else {\n queryFilter = `where: {\n _and: [\n {block_timestamp: {${timeOperation}: ${queryTime}}}\n ]\n }, `;\n }\n\n return `\nquery FeedQuery($offset: Int, $limit: Int) {\n dataplatform_near_social_feed_moderated_posts(${queryFilter} order_by: [${querySortOption} { block_height: desc }], offset: $offset, limit: $limit) {\n account_id\n block_height\n block_timestamp\n content\n receipt_id\n accounts_liked\n last_comment_timestamp\n comments(order_by: {block_height: asc}) {\n account_id\n block_height\n block_timestamp\n content\n }\n verifications {\n human_provider\n human_valid_until\n human_verification_level\n }\n\n }\n dataplatform_near_social_feed_moderated_posts_aggregate(${queryFilter} order_by: {id: asc}) {\n aggregate {\n count\n }\n }\n}\n`;\n};\n\nconst loadMorePosts = (isUpdate) => {\n const queryName = \"FeedQuery\";\n\n if (!isUpdate) {\n setIsLoading(true);\n }\n const offset = isUpdate ? 0 : postsData.posts.length;\n const limit = isUpdate ? 100 : LIMIT;\n const query = createQuery(\"\", isUpdate);\n fetchGraphQL(query, queryName, {\n offset: offset,\n limit: limit,\n }).then((result) => {\n if (result.status === 200 && result.body) {\n if (result.body.errors) {\n console.log(\"error:\", result.body.errors);\n return;\n }\n let data = result.body.data;\n if (data) {\n const newPosts = data.dataplatform_near_social_feed_moderated_posts;\n const postsCountLeft =\n data.dataplatform_near_social_feed_moderated_posts_aggregate.aggregate\n .count;\n if (newPosts.length > 0) {\n let filteredPosts = newPosts.filter(\n (i) => !shouldFilter(i, postsModerationKey)\n );\n filteredPosts = filteredPosts.map((post) => {\n const prevComments = post.comments;\n const filteredComments = prevComments.filter(\n (comment) => !shouldFilter(comment, commentsModerationKey)\n );\n post.comments = filteredComments;\n return post;\n });\n\n if (isUpdate) {\n setNewUnseenPosts(filteredPosts);\n } else {\n setPostsData({\n posts: [...postsData.posts, ...filteredPosts],\n postsCountLeft,\n });\n setIsLoading(false);\n }\n }\n }\n }\n if (!isUpdate && initialQueryTime === null) {\n const newTime =\n postsData.posts && postsData.posts[0]\n ? postsData.posts[0].block_timestamp\n : Date.now() * 1000000;\n setInitialQueryTime(newTime + 1000);\n }\n });\n};\n\nconst startFeedUpdates = () => {\n if (initialQueryTime === null) return;\n\n clearInterval(feedInterval);\n const newFeedInterval = setInterval(() => {\n loadMorePosts(true);\n }, 5000);\n setFeedInterval(newFeedInterval);\n};\n\nconst stopFeedUpdates = () => {\n clearInterval(feedInterval);\n};\n\nconst [initialized, setInitialized] = useState(false);\nconst [initialQueryTime, setInitialQueryTime] = useState(null);\nconst [feedInterval, setFeedInterval] = useState(null);\nconst [newUnseenPosts, setNewUnseenPosts] = useState([]);\nconst [postsData, setPostsData] = useState({ posts: [], postsCountLeft: 0 });\nconst [isLoading, setIsLoading] = useState(false);\n\nuseEffect(() => {\n loadMorePosts(false);\n}, []);\n\nuseEffect(() => {\n if (postsData.posts.length > 0) {\n setPostExists(true);\n }\n}, [postsData]);\n\nuseEffect(() => {\n if (initialQueryTime === null) {\n clearInterval(feedInterval);\n } else {\n startFeedUpdates();\n }\n}, [initialQueryTime]);\n\nuseEffect(() => {\n if (newUnseenPosts && newUnseenPosts.length > 0) {\n stopFeedUpdates();\n const initialQueryTime = newUnseenPosts[0].block_timestamp + 1000; // timestamp is getting rounded by 3 digits\n const newTotalCount = postsData.postsCountLeft + newUnseenPosts.length;\n setPostsData({\n posts: [...newUnseenPosts, ...postsData.posts],\n postsCountLeft: newTotalCount,\n });\n if (props.onNewUnseenPosts) {\n props.onNewUnseenPosts(newUnseenPosts);\n }\n setNewUnseenPosts([]);\n setInitialQueryTime(initialQueryTime);\n }\n}, [newUnseenPosts]);\n\nconst hasMore =\n postsData.postsCountLeft !== postsData.posts.length &&\n postsData.posts.length > 0;\n\nif (!initialized && sort) {\n setInitialized(true);\n}\n\nreturn (\n <>\n <Widget\n src=\"near/widget/Posts.Feed\"\n props={{\n hasMore,\n isLoading,\n loadMorePosts: () => {\n if (!isLoading) {\n loadMorePosts(false);\n }\n },\n posts: postsData.posts,\n showFlagAccountFeature: props.showFlagAccountFeature,\n }}\n />\n </>\n);\n" }, "devhub.entity.community.Teams": { "": "const { handle } = props;\n\nconst { Tile } =\n VM.require(\"devhub.megha19.near/widget/devhub.components.molecule.Tile\") ||\n (() => <></>);\n\nif (!Tile) {\n return <div>Loading...</div>;\n}\n\nconst { getCommunity } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nconst communityData = getCommunity({ handle });\n\nif (communityData === null) {\n return <div>Loading...</div>;\n}\n\nconst UserList = ({ name, users }) => (\n <div>\n {(users ?? []).map((user, i) => (\n <div className={`row ${i < users.length - 1 ? \"mb-3\" : \"\"}`}>\n <div class=\"col-3\">\n <b>{name + \" #\" + (i + 1)}</b>\n </div>\n\n <div class=\"col-9\">\n <span\n key={user}\n className=\"d-inline-flex\"\n style={{ fontWeight: 500 }}\n >\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.ProfileLine\"\n props={{ accountId: user, hideAccountId: true, tooltip: true }}\n />\n </span>\n </div>\n </div>\n ))}\n </div>\n);\n\nreturn (\n <div className=\"d-flex flex-column align-items-center gap-4 w-100 px-2\">\n <Tile className=\"p-3 w-100 bg-white mb-3\" style={{ maxWidth: 960 }}>\n <div>\n <div\n className=\"d-flex align-items-center justify-content-between w-100 pb-3\"\n style={{ minHeight: 30 }}\n >\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n <span>Community Admins</span>\n </h5>\n </div>\n <UserList name=\"Admin\" users={communityData.admins} />\n </div>\n </Tile>\n </div>\n);\n" }, "devhub.entity.team.LabelRow": { "": "/**\n * In the context of the contract, a group is essentially a member identified\n * by the prefix 'team:'; therefore, on the front end, we also employ 'team,'\n * with the user interface displaying 'group' for clarity.\n */\n\nconst { getAccessControlInfo, getRootMembers, removeMember } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nif (!getAccessControlInfo || !getRootMembers || !removeMember) {\n return <p>Loading modules...</p>;\n}\n\nconst accessControlInfo = getAccessControlInfo();\nconst rootMembers = getRootMembers();\nconst allTeamNames = Object.keys(rootMembers || {});\n\nif (!accessControlInfo || !rootMembers) {\n return <p>Loading access control info...</p>;\n}\n\nconst { teamName } = props;\nconst teamModerators = teamName == \"team:moderators\";\nconst label = Object.keys(rootMembers[teamName].permissions)[0] || \"\";\nconst metadata = accessControlInfo.members_list[teamName];\nconst editPost = rootMembers[teamName].permissions[label].includes(\"edit-post\");\nconst useLabels =\n rootMembers[teamName].permissions[label].includes(\"use-labels\");\nconst members = rootMembers[teamName].children || [];\n\nconst configuratorData = {\n teamName: teamName,\n label: label,\n members,\n editPost,\n useLabels,\n};\n\nconst [editMode, setEditMode] = useState(false);\nconst [alertMessage, setAlertMessage] = useState(\"\");\n\nfunction arrayEq(arr1, arr2) {\n if (arr1.length !== arr2.length) {\n return false;\n }\n const sortedArr1 = arr1.slice().sort();\n const sortedArr2 = arr2.slice().sort();\n for (let i = 0; i < sortedArr1.length; i++) {\n if (sortedArr1[i] !== sortedArr2[i]) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction editTeam({\n teamName: tmnm,\n label: lbl,\n editPost: edtpst,\n useLabels: uslbls,\n members: mmbrs,\n}) {\n let txn = [];\n let numberOfChanges = 0;\n\n if (backwardsCompatibleTeam(teamName) !== tmnm) {\n numberOfChanges++;\n if (allTeamNames.includes(`team:${tmnm}`)) {\n return setAlertMessage(\"This team name already exists\");\n }\n }\n\n console.log(label, lbl);\n if (label !== lbl) {\n const allLabels = Object.keys(accessControlInfo.rules_list);\n if (allLabels.includes(lbl)) {\n return setAlertMessage(\n \"This label is already restricted by another team\"\n );\n }\n }\n\n if (editPost !== edtpst || useLabels !== uslbls) {\n numberOfChanges++;\n }\n\n if (!arrayEq(members, mmbrs)) {\n numberOfChanges++;\n let membersAndTeams = Object.keys(accessControlInfo.members_list);\n\n mmbrs.forEach((member) => {\n if (!membersAndTeams.includes(member)) {\n // Contract panic member does not exist in the members_list yet.\n txn.push({\n contractName: \"devhub.near\",\n methodName: \"add_member\",\n args: {\n member: member,\n metadata: {\n member_metadata_version: \"V0\",\n description: \"\",\n permissions: {},\n children: [],\n parents: [],\n },\n },\n gas: Big(10).pow(14),\n });\n }\n });\n }\n\n if (numberOfChanges < 1) {\n return setAlertMessage(\"No changes found.\");\n }\n\n Near.call([\n ...txn,\n {\n contractName: \"devhub.near\",\n methodName: \"edit_member\",\n args: {\n member: `team:${tmnm}`,\n metadata: {\n member_metadata_version: \"V0\",\n description: \"\",\n permissions: {\n [lbl]: [\n ...(edtpst ? [\"edit-post\"] : []),\n ...(uslbls ? [\"use-labels\"] : []),\n ],\n },\n children: mmbrs,\n parents: [],\n },\n },\n gas: Big(10).pow(14),\n },\n ]);\n}\n\nfunction deleteLabel() {\n // contract side this is called a team / member\n removeMember(teamName);\n}\n\nconst backwardsCompatibleLabel = (oldLabel) => {\n if (typeof oldLabel === \"string\")\n return oldLabel.startsWith(\"starts-with:\") ? oldLabel.slice(12) : oldLabel;\n else return \"\";\n};\n// Teams are saved in contract by their prefix 'team:'\n// This function makes the teamName display friendly.\nconst backwardsCompatibleTeam = (oldTeam) =>\n oldTeam.startsWith(\"team:\") ? oldTeam.slice(5) : oldTeam;\n\nreturn (\n <>\n <tr>\n <th scope=\"row\" class=\" justify-content-center align-items-center p-3\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.atom.Tag\"}\n props={{\n tag: backwardsCompatibleLabel(label),\n }}\n />\n </th>\n <td class=\" justify-content-center align-items-center p-3\">\n {(label || \"\").startsWith(\"starts-with:\")\n ? \"Multiple labels with common prefix\"\n : \"Single label\"}\n </td>\n <td class=\" justify-content-center align-items-center p-3\">\n {metadata.children && (\n <div class=\"vstack\">\n {metadata.children.length ? (\n metadata.children.map((child) => <p>{child}</p>)\n ) : (\n <div>No members in this group</div>\n )}\n </div>\n )}\n </td>\n <td class=\" justify-content-center align-items-center p-3\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n value={useLabels}\n id={`useLabelsCheckbox${identifier}`}\n checked={useLabels}\n onChange={() => setUseLabels(!useLabels)}\n disabled={disabled}\n />\n </div>\n </td>\n <td class=\" justify-content-center align-items-center p-3\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n value={editPost}\n id={`editPostCheckbox${identifier}`}\n checked={editPost}\n onChange={() => setEditPost(!editPost)}\n disabled={disabled}\n />\n </div>\n </td>\n <td class=\" justify-content-center align-items-center p-3\">\n {editMode ? (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-danger\" },\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-trash\",\n },\n label: \"Delete\",\n onClick: deleteLabel,\n }}\n />\n ) : (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-light text-dark\" },\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-gear-wide-connected\",\n },\n label: \"Edit\",\n onClick: () => setEditMode(true),\n }}\n />\n )}\n </td>\n </tr>\n {editMode && (\n <tr>\n <th scope=\"row\" colspan=\"6\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.team.Configurator\"}\n props={{\n data: configuratorData,\n onCancel: () => setEditMode(false),\n onSubmit: (params) => editTeam(params),\n }}\n />\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.atom.Alert\"\n props={{\n onClose: () => setAlertMessage(\"\"),\n message: alertMessage,\n }}\n />\n </th>\n </tr>\n )}\n </>\n);\n" }, "devhub.components.organism.NewsLetter": { "": "const page = props.page;\n\nconst Footer = styled.div`\n width: 100%;\n background-color: #00ec97;\n padding: 2rem;\n margin-top: 2rem;\n width: 100vw;\n position: relative;\n left: 50%;\n right: 50%;\n margin-left: -50vw;\n margin-right: -50vw;\n\n @media screen and (max-width: 768px) {\n padding: 1rem;\n }\n`;\n\nconst CTA = styled.a`\n display: inline-flex;\n padding: 0.5rem 0.8rem;\n align-items: center;\n gap: 0.5rem;\n\n border-radius: 0.5rem;\n border: 1px solid #151515;\n\n color: #151515 !important;\n font-size: 0.8rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.48px;\n\n &:hover {\n background: #151515;\n color: #f4f4f4 !important;\n text-decoration: none; // Remove underline on hover\n }\n`;\n\nconst XIcon = () => {\n return (\n <svg\n width=\"20\"\n height=\"16\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 300 300\"\n >\n <path d=\"M178.57 127.15 290.27 0h-26.46l-97.03 110.38L89.34 0H0l117.13 166.93L0 300.25h26.46l102.4-116.59 81.8 116.59h89.34M36.01 19.54H76.66l187.13 262.13h-40.66\" />\n </svg>\n );\n};\n\nconst TelegramIcon = () => {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"20\"\n height=\"16\"\n viewBox=\"0 0 20 16\"\n fill=\"none\"\n >\n <path\n d=\"M19.7398 1.45657L16.8608 15.0342C16.6436 15.9924 16.0771 16.2309 15.2721 15.7796L10.8854 12.5469L8.76879 14.5828C8.53463 14.817 8.33866 15.0129 7.8872 15.0129L8.20233 10.5452L16.3327 3.19847C16.6862 2.88334 16.256 2.70869 15.7833 3.02386L5.73217 9.35266L1.40507 7.99835C0.463838 7.70445 0.446834 7.05707 1.60095 6.60566L18.526 0.085202C19.3096 -0.208647 19.9954 0.25977 19.7398 1.45657Z\"\n fill=\"#151515\"\n />\n </svg>\n );\n};\n\nconst YoutubeIcon = () => {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"16\"\n viewBox=\"0 0 24 16\"\n fill=\"none\"\n >\n <path\n d=\"M23.1744 2.49854C22.9115 1.51517 22.1369 0.740571 21.1535 0.477714C19.3712 -1.21102e-07 12.2235 0 12.2235 0C12.2235 0 5.07581 -1.21102e-07 3.29346 0.477714C2.3101 0.740571 1.53549 1.51517 1.27264 2.49854C0.794922 4.28089 0.794922 8 0.794922 8C0.794922 8 0.794922 11.7191 1.27264 13.5015C1.53549 14.4848 2.3101 15.2594 3.29346 15.5223C5.07581 16 12.2235 16 12.2235 16C12.2235 16 19.3712 16 21.1535 15.5223C22.1369 15.2594 22.9115 14.4848 23.1744 13.5015C23.6521 11.7191 23.6521 8 23.6521 8C23.6521 8 23.6521 4.28089 23.1744 2.49854ZM9.93778 11.4286V4.57143L15.8761 8L9.93778 11.4286Z\"\n fill=\"#151515\"\n />\n </svg>\n );\n};\n\nconst MidContent = () => {\n return (\n <>\n <MidContainer>\n <Title>/dev/hub newsletter</Title>\n <Description>\n Stay in the loop. Get the latest updates, announcements,\n <br />\n opportunities, and insights from the ecosystem in your inbox.\n </Description>\n <CTA href=\"https://newsletter.neardevhub.org\" target=\"no_blank\">\n Subscribe\n </CTA>\n <SocialLinks>\n <a href=\"https://twitter.com/NEARDevHub\" target=\"_blank\">\n <XIcon />\n </a>\n <a href=\"https://t.me/NEARDevHub\" target=\"_blank\">\n <TelegramIcon />\n </a>\n <a href=\"https://www.youtube.com/@NEARDevHub\" target=\"_blank\">\n <YoutubeIcon />\n </a>\n </SocialLinks>\n </MidContainer>\n </>\n );\n};\n\nconst SocialLinksContainer = () => {\n return (\n <div className=\"d-flex gap-md-4 gap-2 align-items-center\">\n <a href=\"https://twitter.com/NEARDevHub\" target=\"_blank\">\n <XIcon />\n </a>\n <a href=\"https://t.me/NEARDevHub\" target=\"_blank\">\n <TelegramIcon />\n </a>\n <a href=\"https://www.youtube.com/@NEARDevHub\" target=\"_blank\">\n <YoutubeIcon />\n </a>\n </div>\n );\n};\n\nreturn (\n <Footer className=\"d-flex gap-2 justify-content-between\">\n <SocialLinksContainer />\n <div className=\"d-flex align-items-center gap-3\">\n <h6 className=\"m-0\">Subscribe to our newsletter</h6>\n <CTA href=\"https://newsletter.neardevhub.org\" target=\"no_blank\">\n Subscribe\n </CTA>\n </div>\n </Footer>\n);\n" }, "devhub.entity.community.Compose": { "": "const profileAccountId = props.profileAccountId;\n\nif (!profileAccountId) {\n return <></>;\n}\n\nState.init({\n image: {},\n text: \"\",\n showPreview: false,\n mentionInput: \"\", // text next to @ tag\n mentionsArray: [], // all the mentions in the description\n});\n\nconst [isSubmittingTransaction, setIsSubmittingTransaction] = useState(false);\nconst profile = Social.getr(`${profileAccountId}/profile`);\nconst autocompleteEnabled = true;\n\nconst content = {\n type: \"md\",\n image: state.image.cid ? { ipfs_cid: state.image.cid } : undefined,\n text: state.text,\n};\n\nfunction extractMentions(text) {\n const mentionRegex =\n /@((?:(?:[a-z\\d]+[-_])*[a-z\\d]+\\.)*(?:[a-z\\d]+[-_])*[a-z\\d]+)/gi;\n mentionRegex.lastIndex = 0;\n const accountIds = new Set();\n for (const match of text.matchAll(mentionRegex)) {\n if (\n !/[\\w`]/.test(match.input.charAt(match.index - 1)) &&\n !/[/\\w`]/.test(match.input.charAt(match.index + match[0].length)) &&\n match[1].length >= 2 &&\n match[1].length <= 64\n ) {\n accountIds.add(match[1].toLowerCase());\n }\n }\n return [...accountIds];\n}\n\nfunction extractTagNotifications(text, item) {\n return extractMentions(text || \"\")\n .filter((accountId) => accountId !== profileAccountId)\n .map((accountId) => ({\n key: accountId,\n value: {\n type: \"mention\",\n item,\n },\n }));\n}\n\nfunction composeData() {\n const data = {\n post: {\n main: JSON.stringify(content),\n },\n index: {\n post: JSON.stringify({\n key: \"main\",\n value: {\n type: \"md\",\n },\n }),\n },\n };\n\n const notifications = extractTagNotifications(state.text, {\n type: \"social\",\n path: `${profileAccountId}/post/main`,\n });\n\n if (notifications.length) {\n data.index.notify = JSON.stringify(\n notifications.length > 1 ? notifications : notifications[0]\n );\n }\n\n return data;\n}\n\nconst handleSubmit = () => {\n const data = composeData();\n if (props.onSubmit) {\n props.onSubmit(data);\n }\n if (props.isFinished) {\n setIsSubmittingTransaction(true);\n }\n};\n\nfunction resetState() {\n State.update({\n image: {},\n text: \"\",\n });\n}\n\nuseEffect(() => {\n if (props.isFinished && props.isFinished() && isSubmittingTransaction) {\n resetState();\n setIsSubmittingTransaction(false);\n }\n}, [props.isFinished]);\n\nfunction textareaInputHandler(value) {\n const words = value.split(/\\s+/);\n const allMentiones = words\n .filter((word) => word.startsWith(\"@\"))\n .map((mention) => mention.slice(1));\n const newMentiones = allMentiones.filter(\n (item) => !state.mentionsArray.includes(item)\n );\n\n State.update((lastKnownState) => ({\n ...lastKnownState,\n text: value,\n showAccountAutocomplete: newMentiones?.length > 0,\n mentionsArray: allMentiones,\n mentionInput: newMentiones?.[0] ?? \"\",\n }));\n}\n\nfunction autoCompleteAccountId(id) {\n // to make sure we update the @ at correct index\n let currentIndex = 0;\n const updatedDescription = state.text.replace(\n /(?:^|\\s)(@[^\\s]*)/g,\n (match) => {\n if (currentIndex === state.mentionsArray.indexOf(state.mentionInput)) {\n currentIndex++;\n return ` @${id}`;\n } else {\n currentIndex++;\n return match;\n }\n }\n );\n State.update((lastKnownState) => ({\n ...lastKnownState,\n text: updatedDescription,\n showAccountAutocomplete: false,\n }));\n}\n\nconst Wrapper = styled.div`\n --padding: 24px;\n position: relative;\n\n @media (max-width: 1024px) {\n --padding: 12px;\n }\n`;\n\nconst LoadingButtonSpinner = (\n <span\n class=\"submit-post-loading-indicator spinner-border spinner-border-sm\"\n role=\"status\"\n aria-hidden=\"true\"\n ></span>\n);\n\nconst Avatar = styled.div`\n width: 40px;\n height: 40px;\n pointer-events: none;\n position: absolute;\n top: var(--padding);\n left: var(--padding);\n\n img {\n object-fit: cover;\n border-radius: 40px;\n width: 100%;\n height: 100%;\n }\n\n @media (max-width: 992px) {\n display: none;\n }\n`;\n\nconst Textarea = styled.div`\n display: grid;\n vertical-align: top;\n align-items: center;\n position: relative;\n align-items: stretch;\n\n &::after,\n textarea {\n width: 100%;\n min-width: 1em;\n height: unset;\n min-height: 164px;\n font: inherit;\n padding: var(--padding) var(--padding) calc(40px + (var(--padding) * 2))\n calc(40px + (var(--padding) * 2));\n margin: 0;\n resize: none;\n background: none;\n appearance: none;\n border: none;\n grid-area: 1 / 1;\n overflow: hidden;\n outline: none;\n\n @media (max-width: 1024px) {\n min-height: 124px;\n }\n\n @media (max-width: 992px) {\n padding-left: var(--padding);\n }\n }\n\n &::after {\n content: attr(data-value) \" \";\n visibility: hidden;\n white-space: pre-wrap;\n }\n\n textarea {\n transition: all 200ms;\n\n &::placeholder {\n opacity: 1;\n color: #687076;\n }\n\n &:empty + p {\n display: block;\n }\n }\n`;\n\nconst TextareaDescription = styled.p`\n position: absolute;\n top: calc(var(--padding) + 24px);\n left: calc(42px + (var(--padding) * 2));\n right: var(--padding);\n font-size: 10px;\n line-height: 18px;\n font-weight: 400;\n color: #687076;\n pointer-events: none;\n display: none;\n\n a {\n color: #000;\n outline: none;\n font-weight: 600;\n pointer-events: auto;\n\n &:hover,\n &:focus {\n color: #000;\n text-decoration: underline;\n }\n }\n\n @media (max-width: 992px) {\n left: var(--padding);\n }\n`;\n\nconst Actions = styled.div`\n display: inline-flex;\n gap: 12px;\n position: absolute;\n bottom: var(--padding);\n right: var(--padding);\n\n .commit-post-button,\n .preview-post-button {\n background: #59e692;\n color: #09342e;\n border-radius: 40px;\n height: 40px;\n padding: 0 35px;\n font-weight: 600;\n font-size: 14px;\n border: none;\n cursor: pointer;\n transition: background 200ms, opacity 200ms;\n\n &:hover,\n &:focus {\n background: rgb(112 242 164);\n outline: none;\n }\n\n &:disabled {\n opacity: 0.5;\n pointer-events: none;\n }\n }\n\n .preview-post-button {\n color: #11181c;\n background: #f1f3f5;\n padding: 0;\n width: 40px;\n\n &:hover,\n &:focus {\n background: #d7dbde;\n outline: none;\n }\n }\n\n .upload-image-button {\n display: flex;\n align-items: center;\n justify-content: center;\n background: #f1f3f5;\n color: #11181c;\n border-radius: 40px;\n height: 40px;\n min-width: 40px;\n font-size: 0;\n border: none;\n cursor: pointer;\n transition: background 200ms, opacity 200ms;\n\n &::before {\n font-size: 16px;\n }\n\n &:hover,\n &:focus {\n background: #d7dbde;\n outline: none;\n }\n\n &:disabled {\n opacity: 0.5;\n pointer-events: none;\n }\n\n span {\n margin-left: 12px;\n }\n }\n\n .d-inline-block {\n display: flex !important;\n gap: 12px;\n margin: 0 !important;\n\n .overflow-hidden {\n width: 40px !important;\n height: 40px !important;\n }\n }\n`;\n\nconst PreviewWrapper = styled.div`\n position: relative;\n padding: var(--padding);\n padding-bottom: calc(40px + (var(--padding) * 2));\n`;\n\nconst AutoComplete = styled.div`\n position: absolute;\n z-index: 5;\n bottom: 0;\n left: 0;\n right: 0;\n\n > div > div {\n padding: calc(var(--padding) / 2);\n }\n`;\n\nreturn (\n <Wrapper>\n {state.showPreview ? (\n <PreviewWrapper>\n <Widget\n src=\"near/widget/v1.Posts.Post\"\n loading={<div className=\"w-100\" style={{ height: \"200px\" }} />}\n props={{\n accountId: profileAccountId,\n blockHeight: \"now\",\n content,\n }}\n />\n </PreviewWrapper>\n ) : (\n <>\n <Avatar>\n <Widget\n src=\"mob.near/widget/Image\"\n props={{\n image: profile.image,\n alt: profile.name,\n fallbackUrl:\n \"https://ipfs.near.social/ipfs/bafkreibiyqabm3kl24gcb2oegb7pmwdi6wwrpui62iwb44l7uomnn3lhbi\",\n }}\n />\n </Avatar>\n\n <Textarea data-value={state.text}>\n <textarea\n data-testid=\"compose-announcement\"\n placeholder=\"What's happening?\"\n onInput={(event) => textareaInputHandler(event.target.value)}\n onKeyUp={(event) => {\n if (event.key === \"Escape\") {\n State.update({ showAccountAutocomplete: false });\n }\n }}\n value={state.text}\n />\n\n <TextareaDescription>\n <a\n href=\"https://www.markdownguide.org/basic-syntax/\"\n target=\"_blank\"\n >\n Markdown\n </a>\n is supported\n </TextareaDescription>\n </Textarea>\n </>\n )}\n\n {autocompleteEnabled && state.showAccountAutocomplete && (\n <AutoComplete>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.AccountAutocomplete\"\n props={{\n term: state.mentionInput,\n onSelect: autoCompleteAccountId,\n onClose: () => State.update({ showAccountAutocomplete: false }),\n }}\n />\n </AutoComplete>\n )}\n\n <Actions>\n {!state.showPreview && (\n <IpfsImageUpload\n image={state.image}\n className=\"upload-image-button bi bi-image\"\n />\n )}\n\n <button\n type=\"button\"\n disabled={!state.text}\n className=\"preview-post-button\"\n title={state.showPreview ? \"Edit Post\" : \"Preview Post\"}\n onClick={() => State.update({ showPreview: !state.showPreview })}\n >\n {state.showPreview ? (\n <i className=\"bi bi-pencil\" />\n ) : (\n <i className=\"bi bi-eye-fill\" />\n )}\n </button>\n\n <button\n data-testid=\"post-btn\"\n disabled={isSubmittingTransaction || (!state.text && !state.image)}\n onClick={handleSubmit}\n className=\"commit-post-button\"\n >\n {isSubmittingTransaction ? LoadingButtonSpinner : <></>}\n Post\n </button>\n </Actions>\n </Wrapper>\n);\n" }, "devhub.components.molecule.BadgesList": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nconst nearDevGovBadgesContractId = \"neardevgov.near\";\n\nlet badges = props.badges;\nconst mode = props.mode || \"normal\";\n\nif (!badges) {\n const accountId = props.accountId || context.accountId;\n const ownedBadges = Near.view(\n nearDevGovBadgesContractId,\n \"nft_tokens_for_owner\",\n {\n account_id: accountId,\n }\n );\n if (!ownedBadges) {\n return <>{mode === \"compact\" ? \"\" : \"Loading...\"}</>;\n }\n badges = ownedBadges;\n}\n\nlet style;\nif (mode === \"normal\") {\n style = { width: \"3em\", height: \"3em\" };\n} else if (mode === \"compact\") {\n style = { width: \"1.5em\", height: \"1.5em\" };\n}\nif (props.style) {\n style = props.style;\n}\n\nconst renderedBadgesList = badges.map(({ token_id: tokenId, metadata }) => (\n <Link\n to={`/neardevgov.near/widget/BadgeDetails?tokenId=${tokenId}`}\n title={`NEAR DevGov Badge - ${metadata.title}`}\n >\n <Widget\n src=\"mob.near/widget/NftImage\"\n props={{\n style,\n nft: {\n tokenMetadata: metadata,\n contractId: nearDevGovBadgesContractId,\n },\n alt: `NEAR DevGov Badge - ${metadata.title}`,\n }}\n />\n {mode === \"compact\" ? null : metadata.title}\n </Link>\n));\n\nif (mode === \"compact\") {\n return <>{renderedBadgesList}</>;\n} else {\n return (\n <ul>\n {renderedBadgesList.map((renderedBadge) => (\n <li style={{ listStyleType: \"none\" }}>{renderedBadge}</li>\n ))}\n </ul>\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}) => {\n State.init({\n data: data,\n selectedTab: \"editor\",\n });\n\n useEffect(() => {\n onChange(state.data);\n }, [state.data]);\n\n useEffect(() => {\n if (data !== state.data) {\n State.update({ data: data, handler: \"autocompleteSelected\" });\n }\n }, [data]);\n\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={() => State.update({ selectedTab: \"editor\" })}\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={\"devhub.megha19.near/widget/devhub.components.molecule.SimpleMDE\"}\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 }}\n />\n </>\n ) : (\n <div className=\"card-body\">\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"\n }\n props={{\n text: state.data,\n }}\n />\n </div>\n )}\n </div>\n </Wrapper>\n );\n};\n\nreturn Compose(props);\n" }, "devhub.entity.post.History": { "": "/*\n---props---\nprops.post: {};\nprops.id: number;\nprops.newTab: boolean;\nprops.timestamp: number;\nprops.referral: any;\n*/\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nconst postId = props.post.id ?? (props.id ? parseInt(props.id) : 0);\nconst post =\n props.post ??\n Near.view(nearDevGovGigsContractAccountId, \"get_post\", {\n post_id: postId,\n });\nif (!post || !post.snapshot_history) {\n return <div class=\"bi bi-clock-history px-2\"></div>;\n}\nconst referral = props.referral;\n\nfunction readableDate(timestamp) {\n var a = new Date(timestamp);\n return (\n a.toDateString() +\n \" \" +\n a.toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\" })\n ).substring(4);\n}\n\nconst currentTimestamp = props.timestamp ?? post.snapshot.timestamp;\nconst snapshot = post.snapshot;\nconst snapshotHistory = post.snapshot_history\n ? Array.from(post.snapshot_history)\n : [];\n\nsnapshotHistory.push(snapshot);\nsnapshotHistory.reverse();\n\nconst history = (\n <div class=\"btn-group\" role=\"group\">\n <a\n class=\"card-link\"\n role=\"button\"\n title=\"Post History\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n type=\"button\"\n >\n <div class=\"bi bi-clock-history px-2\"></div>\n </a>\n <ul class=\"dropdown-menu\">\n <a\n class=\"d-flex text-muted\"\n style={{ fontSize: \"12px\", textDecoration: \"none\", cursor: \"default\" }}\n >\n <a\n style={{\n textAlign: \"center\",\n minWidth: \"290px\",\n maxWidth: \"290px\",\n }}\n >\n Edit History\n </a>\n <a style={{ marginRight: \"8px\" }}>Compare</a>\n </a>\n {snapshotHistory.map((item) => {\n if (item === undefined) return;\n return (\n <li style={{ display: \"flex\" }}>\n <div\n style={{\n minWidth: \"290px\",\n maxWidth: \"290px\",\n }}\n >\n <a\n class=\"dropdown-item\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/devhub.entity.post.Post\",\n params: {\n id: postId,\n timestamp: item.timestamp,\n compareTimestamp: null,\n referral,\n },\n })}\n target={props.newTab ? \"_blank\" : undefined}\n >\n {readableDate(item.timestamp / 1000000)}\n\n <Widget\n src=\"mob.near/widget/ProfileImage\"\n props={{\n accountId: item.editor_id,\n style: {\n width: \"1.25em\",\n height: \"1.25em\",\n },\n imageStyle: {\n transform: \"translateY(-12.5%)\",\n },\n }}\n />\n {post.author_id.substring(0, 8)}\n </a>\n </div>\n <a\n class=\"dropdown-item\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/devhub.entity.post.Post\",\n params: {\n id: postId,\n timestamp: currentTimestamp,\n compareTimestamp: item.timestamp,\n referral,\n },\n })}\n >\n <i class=\"bi bi-file-earmark-diff\" />\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n);\n\nreturn history;\n" }, "devhub.entity.community.configuration.BrandingConfigurator": { "": "const Banner = styled.div`\n border-top-left-radius: var(--bs-border-radius-xl) !important;\n border-top-right-radius: var(--bs-border-radius-xl) !important;\n height: calc(100% - 100px);\n\n & > div :not(.btn) {\n position: absolute;\n display: none;\n margin: 0 !important;\n width: 0 !important;\n height: 0 !important;\n }\n\n .btn {\n padding: 0.5rem 0.75rem !important;\n min-height: 32;\n line-height: 1;\n\n border: none;\n border-radius: 50px;\n --bs-btn-color: #ffffff;\n --bs-btn-bg: #087990;\n --bs-btn-border-color: #087990;\n --bs-btn-hover-color: #ffffff;\n --bs-btn-hover-bg: #055160;\n --bs-btn-hover-border-color: #055160;\n --bs-btn-focus-shadow-rgb: 49, 132, 253;\n --bs-btn-active-color: #ffffff;\n --bs-btn-active-bg: #055160;\n --bs-btn-active-border-color: #055160;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n opacity: 0.8;\n\n &:hover {\n opacity: 1;\n }\n }\n`;\n\nconst Logo = styled.div`\n & > div :not(.btn) {\n position: absolute;\n display: none;\n margin: 0 !important;\n width: 0 !important;\n height: 0 !important;\n }\n\n .btn {\n padding: 0.5rem 0.75rem !important;\n min-height: 32;\n line-height: 1;\n\n border: none;\n border-radius: 50px;\n --bs-btn-color: #ffffff;\n --bs-btn-bg: #087990;\n --bs-btn-border-color: #087990;\n --bs-btn-hover-color: #ffffff;\n --bs-btn-hover-bg: #055160;\n --bs-btn-hover-border-color: #055160;\n --bs-btn-focus-shadow-rgb: 49, 132, 253;\n --bs-btn-active-color: #ffffff;\n --bs-btn-active-bg: #055160;\n --bs-btn-active-border-color: #055160;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n opacity: 0.8;\n\n &:hover {\n opacity: 1;\n }\n }\n`;\n\nconst cidToURL = (cid) => `https://ipfs.near.social/ipfs/${cid}`;\n\nconst { data, onSubmit, hasConfigurePermissions, link } = props;\n\nconst initialInput = { banner: null, logo: null };\n\nconst initialValues = {\n banner: { cid: data.banner_url.split(\"/\").at(-1) },\n logo: { cid: data.logo_url.split(\"/\").at(-1) },\n};\n\nState.init({\n input: initialInput,\n});\n\nconst hasUnsubmittedChanges = Object.values(state.input).some(\n (value) => value !== null\n);\n\nconst isSynced = state.input === initialValues;\n\nif (hasUnsubmittedChanges && !isSynced) {\n onSubmit({\n banner_url: cidToURL(state.input.banner?.cid ?? initialValues.banner.cid),\n logo_url: cidToURL(state.input.logo?.cid ?? initialValues.logo.cid),\n });\n\n State.update((lastKnownState) => ({\n ...lastKnownState,\n input: initialInput,\n }));\n}\n\nreturn (\n <div style={{ height: 280 }}>\n <Banner\n alt=\"Community banner preview\"\n className=\"card-img-top d-flex flex-column justify-content-end align-items-end p-4\"\n style={{\n background: `center / cover no-repeat url(${cidToURL(\n initialValues.banner.cid\n )})`,\n }}\n >\n {hasConfigurePermissions && (\n <IpfsImageUpload image={state.input.banner} />\n )}\n </Banner>\n <Logo\n alt=\"Community logo preview\"\n className={[\n \"d-flex flex-column justify-content-center align-items-center\",\n \"rounded-circle ms-5 border border-4 border-white\",\n ].join(\" \")}\n style={{\n marginTop: -64,\n width: 128,\n height: 128,\n\n background: `center / cover no-repeat url(${cidToURL(\n initialValues.logo.cid\n )})`,\n }}\n >\n {hasConfigurePermissions && <IpfsImageUpload image={state.input.logo} />}\n </Logo>\n\n <div\n className=\"card-body p-4\"\n style={{ marginTop: -64, marginLeft: 180, height: 84 }}\n >\n <h5\n className=\"h5 text-nowrap overflow-hidden\"\n style={{ textOverflow: \"ellipsis\" }}\n >\n {typeof link === \"string\" && link.length > 0 ? (\n <Link to={link}>{data.name}</Link>\n ) : (\n data.name\n )}\n </h5>\n\n <p\n className=\"card-text text-nowrap overflow-hidden\"\n style={{ textOverflow: \"ellipsis\" }}\n >\n {data.description}\n </p>\n </div>\n </div>\n);\n" }, "devhub.components.molecule.Tile": { "": "function Tile({ id, children, className, minHeight, style }) {\n return (\n <div\n id={id}\n className={[\n \"d-flex flex-column rounded-4 attractable w-100 border\",\n className,\n ].join(\" \")}\n style={{\n minHeight: minHeight ?? 180,\n height: \"fit-content\",\n ...style,\n }}\n >\n {children}\n </div>\n );\n}\n\nreturn { Tile };\n" }, "devhub.components.molecule.BadgeDetails": { "": "const nearDevGovBadgesContractId = \"neardevgov.near\";\n\nlet badgeId, ownerAccountId;\nif (props.tokenId) {\n let [_badgeId, _ownerAccountId] = props.tokenId.split(\":\", 2);\n badgeId = _badgeId;\n ownerAccountId = _ownerAccountId;\n} else {\n badgeId = props.badgeId;\n}\n\nif (!badgeId) {\n return (\n <>\n Please, provide <pre>badgeId</pre> or <pre>tokenId</pre> to the\n DevGovBadgeDetails component\n </>\n );\n}\n\nlet badgeMetadata =\n props.badgeMetadata ??\n Near.view(nearDevGovBadgesContractId, \"get_badge\", {\n badge_id: badgeId,\n }).badge_metadata;\n\nif (!badgeMetadata) {\n return <>Loading...</>;\n}\n\nreturn (\n <>\n <div className=\"bg-white shadow rounded overflow-hidden\">\n <div className=\"px-4 pt-0 pb-5 bg-dark position-relative\">\n <div\n className=\"profile-picture d-inline-block\"\n style={{ transform: \"translateY(7rem)\" }}\n >\n <Widget\n src=\"mob.near/widget/NftImage\"\n props={{\n style: { width: \"10em\", height: \"10em\" },\n className: \"rounded-circle w-100 h-100 img-thumbnail d-block\",\n nft: {\n tokenMetadata: badgeMetadata,\n contractId: nearDevGovBadgesContractId,\n },\n alt: badgeMetadata.title,\n }}\n />\n </div>\n </div>\n <div className=\"bg-light px-4 pb-4\">\n <div className=\"d-md-flex justify-content-between pt-3 mb-2\">\n <div style={{ paddingTop: \"3rem\" }}>\n <div className=\"me-2 d-sm-flex gap-1 flex-row align-items-center\">\n <div className=\"me-2 position-relative\">\n <h4 className=\"mt-0 mb-0 text-truncate\">\n {badgeMetadata.title}\n </h4>\n </div>\n </div>\n <div className=\"d-flex flex-row\">\n <div className=\"me-4\">\n <span className=\"text-muted\">Awarded to</span>\n <span className=\"fw-bolder\">{badgeMetadata.copies}</span>\n <span className=\"text-muted\">developers</span>\n </div>\n </div>\n <div>\n <Markdown text={badgeMetadata.description} />\n </div>\n </div>\n </div>\n </div>\n </div>\n </>\n);\n" }, "devhub.entity.community.configuration.InformationConfigurator": { "": "const CommunityInformationSchema = {\n name: {\n inputProps: {\n min: 2,\n max: 30,\n placeholder: \"Community name.\",\n required: true,\n },\n\n label: \"Name\",\n order: 1,\n },\n\n description: {\n inputProps: {\n min: 2,\n max: 60,\n\n placeholder:\n \"Describe your community in one short sentence that will appear in the communities discovery page.\",\n\n required: true,\n },\n\n label: \"Description\",\n order: 2,\n },\n\n handle: {\n inputProps: {\n min: 2,\n max: 40,\n allowCommaAndSpace: false,\n placeholder:\n \"Choose unique URL handle for your community. Example: zero-knowledge.\",\n\n required: true,\n },\n\n label: \"URL handle\",\n order: 3,\n },\n\n tag: {\n inputProps: {\n min: 2,\n max: 30,\n allowCommaAndSpace: false,\n placeholder:\n \"Any posts with this tag will show up in your community feed.\",\n\n required: true,\n },\n\n label: \"Tag\",\n order: 4,\n },\n};\n\nconst { data, onSubmit, onCancel, setIsActive, isActive } = props;\n\nfunction handleOnSubmit(v) {\n onSubmit(v);\n setIsActive(false);\n}\n\nreturn (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.organism.Configurator\"}\n props={{\n externalState: data,\n schema: CommunityInformationSchema,\n onSubmit: handleOnSubmit,\n isActive: isActive,\n onCancel: onCancel,\n }}\n />\n);\n" }, "devhub.entity.community.Tile": { "": "/* INCLUDE: \"core/lib/gui/attractable\" */\nconst AttractableDiv = styled.div`\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\nconst AttractableLink = styled.a`\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\nconst AttractableImage = styled.img`\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/* END_INCLUDE: \"core/lib/gui/attractable\" */\n\nconst Tile = ({\n children,\n borderRadius,\n className,\n fullWidth,\n headerSlotRight,\n heading,\n headingAdornment,\n id,\n isHidden,\n noBorder,\n noFrame,\n minHeight,\n style,\n}) => (\n <AttractableDiv\n className={[\n \"d-flex flex-column gap-3\",\n className,\n fullWidth ? \"w-100\" : \"\",\n !noFrame ? \"p-3\" : \"\",\n isHidden ? \"d-none\" : \"\",\n ].join(\" \")}\n style={{\n maxWidth: fullWidth ? \"100%\" : null,\n minHeight: minHeight ?? 180,\n height: \"fit-content\",\n overflowX: \"auto\",\n borderRadius: 16,\n border: \"1px solid rgba(129, 129, 129, 0.30)\",\n background: \"#FFFEFE\",\n marginBottom: \"1rem\",\n ...style,\n }}\n {...{ id }}\n >\n {\n <div\n className={[\n \"d-flex align-items-center justify-content-between gap-3\",\n\n noFrame || (!heading && !headingAdornment && !headerSlotRight)\n ? \"d-none\"\n : \"\",\n ].join(\" \")}\n style={{ minHeight: 30 }}\n >\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n {headingAdornment}\n <span>{heading}</span>\n </h5>\n\n {headerSlotRight}\n </div>\n }\n\n {children}\n </AttractableDiv>\n);\n\nreturn Tile(props);\n" }, "devhub.entity.proposal.ConfirmCancelModal": { "": "const isOpen = props.isOpen;\nconst onCancelClick = props.onCancelClick;\nconst onConfirmClick = props.onConfirmClick;\n\nconst Modal = styled.div`\n display: ${({ hidden }) => (hidden ? \"none\" : \"flex\")};\n position: fixed;\n inset: 0;\n justify-content: center;\n align-items: center;\n opacity: 1;\n z-index: 999;\n\n .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n\n @media screen and (max-width: 768px) {\n h5 {\n font-size: 16px !important;\n }\n }\n\n .btn {\n font-size: 14px;\n }\n`;\n\nconst ModalBackdrop = styled.div`\n position: absolute;\n inset: 0;\n background-color: rgba(0, 0, 0, 0.5);\n opacity: 0.4;\n`;\n\nconst ModalDialog = styled.div`\n padding: 2em;\n z-index: 999;\n overflow-y: auto;\n max-height: 85%;\n margin-top: 5%;\n width: 50%;\n\n @media screen and (max-width: 768px) {\n margin: 2rem;\n width: 100%;\n }\n`;\n\nconst ModalHeader = styled.div`\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding-bottom: 4px;\n`;\n\nconst ModalFooter = styled.div`\n padding-top: 4px;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: items-center;\n`;\n\nconst CloseButton = styled.button`\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: white;\n padding: 0.5em;\n border-radius: 6px;\n border: 0;\n color: #344054;\n\n &:hover {\n background-color: #d3d3d3;\n }\n`;\n\nconst ConfirmButton = styled.button`\n padding: 0.7em;\n border-radius: 6px;\n border: 0;\n box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);\n background-color: #12b76a;\n color: white;\n\n &:hover {\n background-color: #0e9f5d;\n }\n`;\n\nconst ModalContent = styled.div`\n flex: 1;\n font-size: 14px;\n margin-top: 4px;\n margin-bottom: 4px;\n overflow-y: auto;\n max-height: 50%;\n\n @media screen and (max-width: 768px) {\n font-size: 12px !important;\n }\n`;\n\nconst NoButton = styled.button`\n background: transparent;\n border: none;\n padding: 0;\n margin: 0;\n box-shadow: none;\n`;\n\nreturn (\n <>\n <Modal hidden={!isOpen}>\n <ModalBackdrop />\n <ModalDialog className=\"card\">\n <ModalHeader>\n <h5 className=\"mb-0\">Confirm proposal cancellation</h5>\n </ModalHeader>\n <ModalContent>\n If you cancel this proposal, the status will change to Cancelled and\n indicate to sponsors that this proposal is no longer active or\n relevant. Comments are still open, but you cannot reopen this proposal\n or make additional changes.\n <br /> Are you sure you want to proceed?\n </ModalContent>\n <div className=\"d-flex gap-2 align-items-center justify-content-end mt-2\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-secondary\" },\n label: \"Cancel\",\n onClick: onCancelClick,\n }}\n />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-danger\" },\n label: \"Ready to Cancel\",\n onClick: onConfirmClick,\n }}\n />\n </div>\n </ModalDialog>\n </Modal>\n </>\n);\n" }, "devhub.components.organism.Configurator": { "": "const Struct = VM.require(\"devhub.megha19.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.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"\n }\n props={{\n text: fieldValue,\n }}\n />\n ) : (\n <span>none</span>\n )}\n </ValueView>\n </div>\n <Widget\n src={`devhub.megha19.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.megha19.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.megha19.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" }, "devhub.components.templates.AppLayout": { "": "const Theme = styled.div`\n inset: 73px 0px 0px;\n width: 100%;\n display: flex;\n flex-direction: column;\n overflow-y: scroll;\n padding-top: calc(-1 * var(--body-top-padding));\n background: #f4f4f4;\n .container-xl {\n padding-inline: 0px !important;\n }\n`;\n\nconst Container = styled.div`\n width: 100%;\n`;\n\nconst ContentContainer = styled.div`\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n width: 100%;\n`;\n\nconst AppHeader = ({ page }) => (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.organism.Navbar\"\n props={{\n page: page,\n ...props,\n }}\n />\n);\n\nconst Footer = (props) => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.organism.NewsLetter\"\n props={{\n ...props,\n }}\n />\n );\n};\n\nfunction AppLayout({ page, children }) {\n return (\n <Theme>\n <Container className=\"container-xl\">\n <AppHeader page={page} />\n <ContentContainer>{children}</ContentContainer>\n <Footer page={page} />\n </Container>\n </Theme>\n );\n}\n\nreturn { AppLayout };\n" }, "devhub.components.molecule.MarkdownViewer": { "": "const Wrapper = styled.div`\n p {\n white-space: pre-line; // This ensures text breaks to new line\n\n span {\n white-space: normal; // and this ensures profile links look normal\n }\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 pre {\n background-color: #f4f4f4;\n border: 1px solid #ddd;\n border-radius: 4px;\n padding: 1em;\n overflow-x: auto;\n font-family: \"Courier New\", Courier, monospace;\n }\n\n a {\n color: #04a46e;\n }\n`;\n\nconst Embedded = styled.span`\n white-space: normal;\n\n p {\n white-space: normal;\n }\n`;\n\nconst renderMention =\n props.renderMention ??\n ((accountId) => (\n <span key={accountId} className=\"d-inline-flex\" style={{ fontWeight: 500 }}>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.ProfileLine\"\n props={{\n accountId: accountId.toLowerCase(),\n hideAccountId: true,\n tooltip: true,\n }}\n />\n </span>\n ));\n\nreturn (\n <Wrapper>\n <Markdown text={props.text} onMention={renderMention} />\n </Wrapper>\n);\n" }, "devhub.entity.community.Spawner": { "": "const { typeMatch } = VM.require(\"devhub.megha19.near/widget/core.lib.struct\");\n\nif (!typeMatch) {\n return <p>Loading modules...</p>;\n}\n\nconst { data, onSubmit, onCancel } = props;\n\nconst CommunityInputsPartialSchema = {\n handle: {\n inputProps: {\n min: 2,\n max: 40,\n allowCommaAndSpace: false,\n placeholder:\n \"Choose unique URL handle for your community. Example: zero-knowledge.\",\n required: true,\n },\n\n label: \"URL handle\",\n order: 3,\n },\n\n name: {\n inputProps: {\n min: 2,\n max: 30,\n placeholder: \"Community name.\",\n required: true,\n },\n\n label: \"Name\",\n order: 1,\n },\n\n tag: {\n inputProps: {\n min: 2,\n max: 30,\n allowCommaAndSpace: false,\n placeholder:\n \"Any posts with this tag will show up in your community feed.\",\n\n required: true,\n },\n\n label: \"Tag\",\n order: 4,\n },\n\n description: {\n inputProps: {\n min: 2,\n max: 60,\n\n placeholder:\n \"Describe your community in one short sentence that will appear in the communities discovery page.\",\n\n required: true,\n },\n\n label: \"Description\",\n order: 2,\n },\n};\n\nconst communityInputsValidator = (formValues) =>\n typeMatch(formValues) &&\n Object.values(formValues).every(\n (value) => typeof value === \"string\" && value.length > 0\n );\n\nconst CommunityInputsDefaults = {\n handle: \"\",\n name: \"\",\n tag: \"\",\n description: \"\",\n};\n\nreturn (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.organism.Configurator\"\n props={{\n heading: \"Community information\",\n externalState: CommunityInputsDefaults,\n fullWidth: true,\n isActive: true,\n isUnlocked: true,\n isValid: communityInputsValidator,\n onSubmit: onSubmit,\n schema: CommunityInputsPartialSchema,\n submitIcon: {\n type: \"bootstrap_icon\",\n variant: \"bi-rocket-takeoff-fill\",\n },\n submitLabel: \"Launch\",\n onCancel: onCancel,\n }}\n />\n);\n" }, "core.lib.stringUtils": { "": "/**\n * Transform input into a consistent and standardized format\n *\n * @param {string} text - The input to normalize.\n * @returns {string} - normalized input\n */\n\nconst normalize = (text) =>\n text\n .replaceAll(/[- \\.]/g, \"_\")\n .replaceAll(/[^\\w]+/g, \"\")\n .replaceAll(/_+/g, \"-\")\n .replace(/^-+/, \"\")\n .replace(/-+$/, \"\")\n .toLowerCase()\n .trim(\"-\");\n\nreturn { normalize };\n" }, "devhub.components.molecule.Switch": { "": "const { className, currentValue, isHidden, key, onChange, options, title } =\n props;\n\nreturn (\n <div\n className={[\n \"btn-group shadow\",\n className ?? \"\",\n isHidden ?? false ? \"d-none\" : \"\",\n ].join(\" \")}\n role=\"group\"\n aria-label={title}\n key={`${key}-${value}`}\n {...{ title }}\n >\n {options.map(({ label, value }) => (\n <>\n <input\n checked={currentValue === value}\n className=\"btn-check\"\n id={`${key}-${value}`}\n name={`${key}-${value}`}\n type=\"radio\"\n {...{ onChange, value }}\n />\n\n <label\n className={[\n \"btn btn-sm\",\n currentValue === value ? \"btn-dark\" : \"btn-light\",\n ].join(\" \")}\n for={`${key}-${value}`}\n >\n {label}\n </label>\n </>\n ))}\n </div>\n);\n" }, "devhub.entity.addon.blog.editor.preview": { "": "return (\n <div>\n <h1>Preview</h1>\n <p>{JSON.stringify(props)}</p>\n </div>\n);\n" }, "devhub.components.island.hero": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nhref || (href = () => {});\n\nconst imageLink =\n \"https://ipfs.near.social/ipfs/bafybeiap2mzwsly4apaldxguiunx4rjwqyadksj5yxuzwrww3kue3ao5qe\";\n\nconst HeroSection = styled.div`\n position: relative;\n height: auto;\n z-index: 3;\n width: 70%;\n background: #00ec97;\n clip-path: polygon(0 0, 100% 0%, 75% 100%, 0% 100%);\n\n padding-top: 2rem;\n padding-bottom: 2rem;\n padding-left: 3.375rem;\n\n @media screen and (max-width: 768px) {\n width: 100%;\n padding: 1rem 1.5rem;\n clip-path: none;\n }\n`;\n\nconst Title = styled.h1`\n color: #f4f4f4;\n font-size: 4rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 88px */\n letter-spacing: -1.76px;\n\n @media screen and (max-width: 768px) {\n font-size: 2.25rem;\n letter-spacing: -0.72px;\n margin: 0;\n }\n`;\n\nconst Lead = styled.p`\n color: #151515;\n font-size: 1.75rem;\n font-style: normal;\n font-weight: 400;\n line-height: 110%; /* 39.6px */\n\n width: 70%;\n\n @media screen and (max-width: 768px) {\n font-size: 1.5rem;\n width: 100%;\n }\n`;\n\nconst CTA = styled.a`\n display: inline-flex;\n padding: 0.875rem 1rem;\n align-items: center;\n gap: 0.5rem;\n\n border-radius: 1rem;\n border: 1px solid #151515;\n\n color: #151515 !important;\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.48px;\n\n &:hover {\n background: #151515;\n color: #f4f4f4 !important;\n text-decoration: none; // Remove underline on hover\n }\n\n @media screen and (max-width: 768px) {\n display: inline-flex;\n padding: 8px 16px;\n align-items: center;\n gap: 8px;\n\n border-radius: 16px;\n background: #00ec97;\n\n border: none;\n\n color: #f4f4f4 !important;\n font-size: 16px;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 19.2px */\n letter-spacing: -0.32px;\n\n &:hover {\n background: #151515;\n color: #f4f4f4;\n text-decoration: none; // Remove underline on hover\n }\n }\n`;\n\nconst Container = styled.div`\n position: relative;\n width: 100%;\n height: max-content;\n overflow: hidden;\n\n @media screen and (max-width: 768px) {\n background: #f4f4f4;\n }\n`;\n\nconst ImageContainer = styled.div`\n width: 100%;\n height: 100%;\n position: absolute;\n top: 0;\n right: 0;\n z-index: 1;\n background: transparent;\n\n @media screen and (max-width: 768px) {\n display: none;\n }\n`;\n\nconst Image = styled.img`\n margin-left: 15.625rem;\n height: 100%;\n width: 100%;\n filter: grayscale(100%);\n object-fit: cover;\n`;\n\nconst DesktopDescription = styled.div`\n @media screen and (max-width: 786px) {\n display: none;\n }\n`;\n\nconst MobileImage = styled.img`\n display: none;\n\n width: 100%;\n height: 196px;\n\n width: 100%;\n object-fit: cover;\n filter: grayscale(1);\n\n @media screen and (max-width: 768px) {\n display: block;\n }\n`;\n\nconst MobileDescription = styled.div`\n display: none;\n padding: 24px 16px;\n\n width: 100%;\n\n @media screen and (max-width: 768px) {\n display: block;\n }\n`;\n\nreturn (\n <Container>\n <HeroSection>\n <Title>\n The decentralized <br />\n <span style={{ color: \"#101820\" }}>home base</span> <br />\n for NEAR builders\n </Title>\n <DesktopDescription>\n <Lead>\n Join a vibrant community of innovators shaping the open web.\n </Lead>\n <Link\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"about\",\n },\n })}\n >\n <CTA href=\"#\">Read more →</CTA>\n </Link>\n </DesktopDescription>\n </HeroSection>\n <MobileImage src={imageLink} />\n <ImageContainer>\n <Image src={imageLink} />\n </ImageContainer>\n <MobileDescription>\n <Lead>Join a vibrant community of innovators shaping the open web.</Lead>\n <Link\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"about\",\n },\n })}\n >\n <CTA href=\"#\">Read more →</CTA>\n </Link>\n </MobileDescription>\n </Container>\n);\n" }, "devhub.entity.post.Panel": { "": "//////////////////////////////////////////////////////////////////////\n///STOPWORDS//////////////////////////////////////////////////////////\nconst stopWords = [\n \"about\",\n \"above\",\n \"after\",\n \"again\",\n \"against\",\n \"all\",\n \"and\",\n \"any\",\n \"are\",\n \"because\",\n \"been\",\n \"before\",\n \"being\",\n \"below\",\n \"between\",\n \"both\",\n \"but\",\n \"can\",\n \"cannot\",\n \"could\",\n \"did\",\n \"does\",\n \"doing\",\n \"down\",\n \"during\",\n \"each\",\n \"etc\",\n \"few\",\n \"for\",\n \"from\",\n \"further\",\n \"had\",\n \"has\",\n \"have\",\n \"having\",\n \"her\",\n \"hers\",\n \"herself\",\n \"him\",\n \"himself\",\n \"his\",\n \"how\",\n \"into\",\n \"its\",\n \"itself\",\n \"just\",\n \"more\",\n \"most\",\n \"myself\",\n \"nor\",\n \"not\",\n \"now\",\n \"off\",\n \"once\",\n \"only\",\n \"other\",\n \"our\",\n \"ours\",\n \"ourselves\",\n \"out\",\n \"over\",\n \"own\",\n \"same\",\n \"she\",\n \"should\",\n \"some\",\n \"still\",\n \"such\",\n \"than\",\n \"that\",\n \"the\",\n \"their\",\n \"theirs\",\n \"them\",\n \"themselves\",\n \"then\",\n \"there\",\n \"these\",\n \"they\",\n \"this\",\n \"those\",\n \"through\",\n \"too\",\n \"under\",\n \"until\",\n \"very\",\n \"was\",\n \"were\",\n \"what\",\n \"when\",\n \"where\",\n \"which\",\n \"while\",\n \"who\",\n \"whom\",\n \"why\",\n \"will\",\n \"with\",\n \"you\",\n \"your\",\n \"yours\",\n \"yourself\",\n \"yourselves\",\n \"www\",\n \"http\",\n \"com\",\n];\n\nconst stopWordsDictionary = {};\nfor (let i = 0; i < stopWords.length; i++) {\n stopWordsDictionary[stopWords[i]] = true;\n}\n\nfunction isStopWord(word) {\n return stopWordsDictionary.hasOwnProperty(word.toLowerCase());\n}\n//////////////////////////////////////////////////////////////////////\n///SYNONYMS///////////////////////////////////////////////////////////\nconst synonyms = {\n ether: \"ethereum\",\n eth: \"ethereum\",\n either: \"ethereum\",\n app: \"application\",\n cryptocyrrency: \"crypto\",\n developerdao: \"devdao\",\n dev: \"develop\",\n doc: \"document\",\n lib: \"librari\",\n saw: \"see\",\n seen: \"see\",\n tweet: \"twitter\",\n paid: \"pai\",\n src: \"sourc\",\n};\n\nconst applySynonym = (word) => {\n if (synonyms.hasOwnProperty(word.toLowerCase())) {\n return synonyms[word];\n }\n return word;\n};\n//////////////////////////////////////////////////////////////////////\n///STEMMING///////////////////////////////////////////////////////////\nconst step2list = {\n ational: \"ate\",\n tional: \"tion\",\n enci: \"ence\",\n anci: \"ance\",\n izer: \"ize\",\n bli: \"ble\",\n alli: \"al\",\n entli: \"ent\",\n eli: \"e\",\n ousli: \"ous\",\n ization: \"ize\",\n ation: \"ate\",\n ator: \"ate\",\n alism: \"al\",\n iveness: \"ive\",\n fulness: \"ful\",\n ousness: \"ous\",\n aliti: \"al\",\n iviti: \"ive\",\n biliti: \"ble\",\n logi: \"log\",\n};\n\n/** @type {Record<string, string>} */\nconst step3list = {\n icate: \"ic\",\n ative: \"\",\n alize: \"al\",\n iciti: \"ic\",\n ical: \"ic\",\n ful: \"\",\n ness: \"\",\n};\n\nconst gt0 = /^([^aeiou][^aeiouy]*)?([aeiouy][aeiou]*)([^aeiou][^aeiouy]*)/;\nconst eq1 =\n /^([^aeiou][^aeiouy]*)?([aeiouy][aeiou]*)([^aeiou][^aeiouy]*)([aeiouy][aeiou]*)?$/;\nconst gt1 =\n /^([^aeiou][^aeiouy]*)?(([aeiouy][aeiou]*)([^aeiou][^aeiouy]*)){2,}/;\nconst vowelInStem = /^([^aeiou][^aeiouy]*)?[aeiouy]/;\nconst consonantLike = /^([^aeiou][^aeiouy]*)[aeiouy][^aeiouwxy]$/;\n\n// Exception expressions.\nconst sfxLl = /ll$/;\nconst sfxE = /^(.+?)e$/;\nconst sfxY = /^(.+?)y$/;\nconst sfxIon = /^(.+?(s|t))(ion)$/;\nconst sfxEdOrIng = /^(.+?)(ed|ing)$/;\nconst sfxAtOrBlOrIz = /(at|bl|iz)$/;\nconst sfxEED = /^(.+?)eed$/;\nconst sfxS = /^.+?[^s]s$/;\nconst sfxSsesOrIes = /^.+?(ss|i)es$/;\nconst sfxMultiConsonantLike = /([^aeiouylsz])\\1$/;\nconst step2 =\n /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;\nconst step3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;\nconst step4 =\n /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;\n\n/**\n * Get the stem from a given value.\n *\n * @param {string} value\n * Value to stem.\n * @returns {string}\n * Stem for `value`\n */\n// eslint-disable-next-line complexity\nfunction stemmer(value) {\n let result = value.toLowerCase();\n\n // Exit early.\n if (result.length < 3) {\n return result;\n }\n\n /** @type {boolean} */\n let firstCharacterWasLowerCaseY = false;\n\n // Detect initial `y`, make sure it never matches.\n if (\n result.codePointAt(0) === 121 // Lowercase Y\n ) {\n firstCharacterWasLowerCaseY = true;\n result = \"Y\" + result.slice(1);\n }\n\n // Step 1a.\n if (sfxSsesOrIes.test(result)) {\n // Remove last two characters.\n result = result.slice(0, -2);\n } else if (sfxS.test(result)) {\n // Remove last character.\n result = result.slice(0, -1);\n }\n\n /** @type {RegExpMatchArray|null} */\n let match;\n\n // Step 1b.\n if ((match = sfxEED.exec(result))) {\n if (gt0.test(match[1])) {\n // Remove last character.\n result = result.slice(0, -1);\n }\n } else if ((match = sfxEdOrIng.exec(result)) && vowelInStem.test(match[1])) {\n result = match[1];\n\n if (sfxAtOrBlOrIz.test(result)) {\n // Append `e`.\n result += \"e\";\n } else if (sfxMultiConsonantLike.test(result)) {\n // Remove last character.\n result = result.slice(0, -1);\n } else if (consonantLike.test(result)) {\n // Append `e`.\n result += \"e\";\n }\n }\n\n // Step 1c.\n if ((match = sfxY.exec(result)) && vowelInStem.test(match[1])) {\n // Remove suffixing `y` and append `i`.\n result = match[1] + \"i\";\n }\n\n // Step 2.\n if ((match = step2.exec(result)) && gt0.test(match[1])) {\n result = match[1] + step2list[match[2]];\n }\n\n // Step 3.\n if ((match = step3.exec(result)) && gt0.test(match[1])) {\n result = match[1] + step3list[match[2]];\n }\n\n // Step 4.\n if ((match = step4.exec(result))) {\n if (gt1.test(match[1])) {\n result = match[1];\n }\n } else if ((match = sfxIon.exec(result)) && gt1.test(match[1])) {\n result = match[1];\n }\n\n // Step 5.\n if (\n (match = sfxE.exec(result)) &&\n (gt1.test(match[1]) ||\n (eq1.test(match[1]) && !consonantLike.test(match[1])))\n ) {\n result = match[1];\n }\n\n if (sfxLl.test(result) && gt1.test(result)) {\n result = result.slice(0, -1);\n }\n\n // Turn initial `Y` back to `y`.\n if (firstCharacterWasLowerCaseY) {\n result = \"y\" + result.slice(1);\n }\n\n return result;\n}\n\n//////////////////////////////////////////////////////////////////////\n///SPELLCHECK/////////////////////////////////////////////////////////\nfunction levenshteinDistance(s, t, threshold) {\n const BIG_NUMBER = 10000;\n if (s == null || t == null) {\n return BIG_NUMBER;\n }\n if (threshold < 0) {\n return BIG_NUMBER;\n }\n let n = s.length;\n let m = t.length;\n if (Math.abs(n - m) >= threshold) {\n return BIG_NUMBER;\n }\n\n // if one string is empty, the edit distance is necessarily the length of the other\n if (n == 0) {\n return m <= threshold ? m : BIG_NUMBER;\n } else if (m == 0) {\n return n <= threshold ? n : BIG_NUMBER;\n }\n\n if (n > m) {\n // swap the two strings to consume less memory\n let temp = s;\n s = t;\n t = temp;\n let tempSize = n;\n n = m;\n m = tempSize;\n }\n\n let p = Array.from({ length: n + 1 }, () => 0); // 'previous' cost array, horizontally\n let d = Array.from({ length: n + 1 }, () => 0); // cost array, horizontally\n let _d; // placeholder to assist in swapping p and d\n\n // fill in starting table values\n const boundary = Math.min(n, threshold) + 1;\n for (let i = 0; i < boundary; i++) {\n p[i] = i;\n }\n // these fills ensure that the value above the rightmost entry of our\n // stripe will be ignored in following loop iterations\n for (let i = boundary; i < p.length; i++) {\n p[i] = BIG_NUMBER;\n }\n for (let i = 0; i < d.length; i++) {\n d[i] = BIG_NUMBER;\n }\n\n // iterates through t\n for (let j = 1; j <= m; j++) {\n const t_j = t.charAt(j - 1); // jth character of t\n d[0] = j;\n\n // compute stripe indices, constrain to array size\n const min = Math.max(1, j - threshold);\n const max = j > BIG_NUMBER - threshold ? n : Math.min(n, j + threshold);\n\n // the stripe may lead off of the table if s and t are of different sizes\n if (min > max) {\n return BIG_NUMBER;\n }\n\n // ignore entry left of leftmost\n if (min > 1) {\n d[min - 1] = BIG_NUMBER;\n }\n\n // iterates through [min, max] in s\n for (let i = min; i <= max; i++) {\n if (s.charAt(i - 1) == t_j) {\n // diagonally left and up\n d[i] = p[i - 1];\n } else {\n // 1 + minimum of cell to the left, to the top, diagonally left and up\n d[i] = 1 + Math.min(Math.min(d[i - 1], p[i]), p[i - 1]);\n }\n }\n\n // copy current distance counts to 'previous row' distance counts\n _d = p;\n p = d;\n d = _d;\n }\n // we don't need to check for threshold here because we did it inside the loop\n return p[n] <= threshold ? p[n] : BIG_NUMBER;\n}\n\nconst spellcheckQueryProcessing = (query, dictionary) => {\n // Split text document into words\n const words = stemAndFilterQuery(query);\n const dictionaryArray = Object.keys(dictionary);\n // Iterate over each word in the text\n for (let i = 0; i < words.length; i++) {\n let word = words[i].toLowerCase().replace(/[^a-z0-9]/g, \"\");\n\n // If the word is not in the dictionary, find the closest match\n if (!dictionary.hasOwnProperty(word)) {\n let closestMatch = undefined;\n let closestDistance = word.length;\n let allowedDistance = Math.min(word.length - 1, 3);\n // Iterate over each word in the dictionary\n if (word.length > 1) {\n for (let j = 0; j < dictionaryArray.length; j++) {\n let dictWord = dictionaryArray[j];\n let distance = levenshteinDistance(word, dictWord, allowedDistance);\n\n // If the distance is less than the closest distance, update the closest match\n if (distance <= allowedDistance && distance < closestDistance) {\n closestMatch = dictWord;\n closestDistance = distance;\n }\n }\n }\n // Replace the misspelled word with the closest match\n words[i] = closestMatch;\n }\n }\n return words.filter((word) => !!word);\n};\n\n//////////////////////////////////////////////////////////////////////\n///INDEXER&SEARCH/////////////////////////////////////////////////////\nconst fillDictionaryWith = (dict, text, id) => {\n let word = \"\";\n for (let i = 0; i < text.length; i++) {\n const char = text.charAt(i);\n const nextChar = text.charAt(i + 1);\n if (/\\w/.test(char) || (char === \".\" && /\\w/.test(nextChar))) {\n word += char.toLowerCase();\n } else if (word.length > 0) {\n const processedWord = applySynonym(stemmer(word));\n if (processedWord.length > 1 && !isStopWord(processedWord)) {\n const oldValue = dict[processedWord] || [];\n dict[processedWord] = [...oldValue, id];\n }\n word = \"\";\n }\n }\n const processedWord = applySynonym(stemmer(word));\n if (processedWord.length > 1 && !isStopWord(processedWord)) {\n const oldValue = dict[stemmer(processedWord)] || [];\n dict[stemmer(processedWord)] = [...oldValue, id];\n }\n return dict;\n};\n\nconst buildIndex = (posts) => {\n let index = {};\n\n posts.forEach((post) => {\n const title = post.snapshot.name;\n const labels = post.snapshot.labels.join(\" \");\n const text = post.snapshot.description;\n const postType = post.snapshot.post_type;\n const authorId = post.author_id;\n const postText = `${authorId} ${postType} ${title} ${labels} ${text}`;\n index = fillDictionaryWith(index, postText, post.id);\n });\n return index;\n};\n\nconst stemAndFilterQuery = (query) => {\n return Object.keys(fillDictionaryWith({}, query));\n};\n\nconst sortSearchResult = (searchResult) => {\n // create a map to count the frequency of each element\n const freq = new Map();\n for (const num of searchResult) {\n freq.set(num, (freq.get(num) || 0) + 1);\n }\n\n // define a custom comparison function to sort the array\n function compare(a, b) {\n // compare the frequency of the two elements\n const freqDiff = freq.get(b) - freq.get(a);\n if (freqDiff !== 0) {\n return freqDiff; // if they have different frequency, sort by frequency\n } else {\n return 0; // if they have the same frequency, leave as it is. Will be sorted by search term, by date\n }\n }\n\n // sort the array using the custom comparison function\n searchResult.sort(compare);\n return searchResult.filter(\n (elem, index) => searchResult.indexOf(elem) === index\n );\n};\n\nconst search = (processedQueryArray, index) => {\n return sortSearchResult(\n processedQueryArray.flatMap((queryWord) => {\n const termSearchRes = index[queryWord].reverse();\n const termSortedSearchRes = sortSearchResult(termSearchRes);\n return termSortedSearchRes;\n })\n );\n};\n\n//////////////////////////////////////////////////////////////////////\n///UI&UX//////////////////////////////////////////////////////////////\n//Run search and spelling computation every time the search bar modified\n//but no more frequent than 1 time per 1.5 seconds\nconst amountOfResultsToShowFirst = 5;\n\nconst buildPostsIndex = () => {\n return Near.asyncView(\"devgovgigs.near\", \"get_posts\").then((posts) => {\n const index = buildIndex(posts);\n const data = posts.reduce((acc, post) => {\n acc[post.id] = post;\n return acc;\n }, {});\n return { index, data };\n });\n};\n\nconst getProcessedPostsCached = () => {\n return useCache(() => buildPostsIndex(), \"processedPostsCached\");\n};\n\nif (!state.interval) {\n let termStorage = \"\";\n Storage.privateSet(\"term\", \"\");\n setInterval(() => {\n const currentInput = Storage.privateGet(\"term\");\n if (currentInput !== termStorage) {\n termStorage = currentInput;\n computeResults(termStorage);\n }\n }, 1500);\n State.update({\n interval: true,\n });\n}\n\nconst computeResults = (term) => {\n const start = new Date().getTime();\n const processedPostsCached = useCache(\n () =>\n buildPostsIndex().then((processedPosts) => {\n // Run query first time posts retrieved\n const query = term;\n const processedQuery = spellcheckQueryProcessing(\n query,\n processedPosts.index\n );\n const searchResult = search(processedQuery, processedPosts.index);\n console.log(processedQuery);\n console.log(searchResult);\n State.update({\n searchResult,\n shownSearchResults: searchResult.slice(0, amountOfResultsToShowFirst),\n processedQuery,\n loading: false,\n });\n return processedPosts;\n }),\n \"processedPostsCached\"\n );\n if (processedPostsCached) {\n // Run query every other time after data retrieved and cached\n const query = term;\n const processedQuery = spellcheckQueryProcessing(\n query,\n processedPostsCached.index\n );\n const searchResult = search(processedQuery, processedPostsCached.index);\n console.log(processedQuery);\n console.log(searchResult);\n State.update({\n searchResult,\n shownSearchResults: searchResult.slice(0, 10),\n processedQuery,\n loading: false,\n });\n }\n const end = new Date().getTime();\n console.log(\"search time: \", end - start);\n};\n\nconst updateInput = (term) => {\n Storage.privateSet(\"term\", term);\n State.update({\n term,\n loading: true,\n });\n};\n\nconst getSearchResultsKeywordsFor = (postId) => {\n const index = getProcessedPostsCached().index;\n return state.processedQuery.filter((queryWord) => {\n return index[queryWord].includes(postId);\n });\n};\n\nconst showMoreSearchResults = () => {\n const shownSearchResults = state.shownSearchResults || [];\n const newShownSearchResults = state.searchResult.slice(\n 0,\n shownSearchResults.length + amountOfResultsToShowFirst\n );\n State.update({ shownSearchResults: newShownSearchResults });\n};\n\nreturn (\n <>\n <div className=\"d-flex flex-row gap-4\">\n <div className=\"d-flex flex-row position-relative w-25\">\n <div className=\"position-absolute d-flex ps-3 flex-column h-100 justify-center\">\n {state.loading ? (\n <span\n className=\"spinner-grow spinner-grow-sm m-auto\"\n role=\"status\"\n aria-hidden=\"true\"\n />\n ) : (\n <i class=\"bi bi-search m-auto\"></i>\n )}\n </div>\n <input\n type=\"search\"\n className=\"ps-5 form-control border border-0 bg-light\"\n value={state.term ?? \"\"}\n onChange={(e) => updateInput(e.target.value)}\n placeholder={props.placeholder ?? `Search Posts`}\n />\n </div>\n <div class=\"dropdown\">\n <button\n class=\"btn btn-light dropdown-toggle\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n >\n Sort\n </button>\n <ul class=\"dropdown-menu px-2 shadow\">\n <li>\n <a\n style={{ borderRadius: \"5px\" }}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n >\n Latest\n </a>\n </li>\n <li>\n <a\n style={{ borderRadius: \"5px\" }}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n >\n Hottest\n </a>\n </li>\n <li>\n <a\n style={{ borderRadius: \"5px\" }}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n >\n All replies\n </a>\n </li>\n </ul>\n </div>\n <div class=\"dropdown\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.feature.post-search.by-author\"\n props={{\n authorQuery: props.authorQuery,\n onAuthorSearch: props.onAuthorSearch,\n }}\n />\n </div>\n <div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.feature.post-search.by-tag\"\n props={{\n tagQuery: props.tagQuery,\n onTagSearch: props.onTagSearch,\n }}\n />\n </div>\n <div className=\"d-flex flex-row-reverse flex-grow-1\">\n {props.children}\n </div>\n </div>\n {state.processedQuery &&\n state.processedQuery.length > 0 &&\n state.term.toLowerCase().trim() !== state.processedQuery.join(\" \") && (\n <div class=\"mb-2\" style={{ \"font-family\": \"monospace\" }}>\n Looking for\n <strong>{state.processedQuery.join(\" \")}</strong>:\n </div>\n )}\n {state.term && state.term.length > 1 && state.searchResult ? (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.post.List\"}\n props={{\n searchResult: {\n postIds: state.searchResult,\n keywords: Object.fromEntries(\n state.searchResult.map((postId) => {\n return [postId, getSearchResultsKeywordsFor(postId)];\n })\n ),\n },\n recency: props.recency,\n tag: props.tag,\n author: props.author,\n }}\n key={key}\n />\n ) : (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.post.List\"}\n props={{\n recency: props.recency,\n tag: props.tag,\n author: props.author,\n transactionHashes: props.transactionHashes,\n }}\n key={key}\n />\n )}\n </>\n);\n" }, "devhub.components.organism.Navbar": { "": "const page = props.page;\n\nconst [showMenu, setShowMenu] = useState(false);\n\nconst { href: linkHref } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nconst { hasModerator } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nlinkHref || (linkHref = () => {});\n\nconst Logo = () => {\n const Wrapper = styled.div`\n @media screen and (max-width: 768px) {\n svg {\n width: 90px;\n height: 12px;\n transform: scale(1.5);\n margin-left: 1rem;\n }\n }\n `;\n\n return (\n <Wrapper>\n <Link\n to={linkHref({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"home\" },\n })}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"180\"\n height=\"24\"\n viewBox=\"0 0 180 24\"\n fill=\"none\"\n >\n <g clip-path=\"url(#clip0_530_29)\">\n <path\n d=\"M8.62185 6.09766C11.8428 6.09766 14.5995 7.77588 14.5995 12.7821V20.8888H10.508V13.1803C10.508 10.7057 9.55041 9.39721 7.49016 9.39721C5.37187 9.39721 4.15313 10.8763 4.15313 13.4079V20.8888H0.0616455V6.26832H3.63081L4.00804 8.08877C4.96563 6.95099 6.32945 6.09766 8.62185 6.09766ZM23.911 21.0594C18.9489 21.0594 15.9601 18.1297 15.9601 13.607C15.9601 9.05588 19.007 6.09766 23.6208 6.09766C28.0895 6.09766 31.1073 8.82832 31.1654 13.1234C31.1654 13.5501 31.1364 14.0337 31.0493 14.4888H20.2257V14.6879C20.3128 16.7643 21.6766 18.0159 23.7369 18.0159C25.3909 18.0159 26.5516 17.3048 26.8998 15.9394H30.9332C30.4689 18.7839 27.8864 21.0594 23.911 21.0594ZM20.3128 11.8719H27.0449C26.7547 10.0799 25.5069 9.08432 23.6498 9.08432C21.8797 9.08432 20.5449 10.1368 20.3128 11.8719ZM47.0396 17.5039H47.5039V20.8888H45.4146C43.2963 20.8888 42.6289 19.8932 42.6579 18.4994C41.6133 20.1208 40.1044 21.0594 37.783 21.0594C34.562 21.0594 32.2406 19.5519 32.2406 16.7643C32.2406 13.6639 34.6201 11.9003 39.0888 11.9003H42.0486V11.1892C42.0486 9.88077 41.091 9.02743 39.3789 9.02743C37.783 9.02743 36.7093 9.73854 36.5352 10.8194H32.5888C32.879 7.97499 35.5486 6.09766 39.495 6.09766C43.6736 6.09766 46.082 7.9181 46.082 11.4168V16.5937C46.082 17.3617 46.4012 17.5039 47.0396 17.5039ZM42.0486 14.8585V14.5741H39.0598C37.3477 14.5741 36.3611 15.2568 36.3611 16.4799C36.3611 17.5039 37.2026 18.1581 38.5665 18.1581C40.7138 18.1581 42.0196 16.8497 42.0486 14.8585ZM56.8924 6.26832H57.5889V9.90921H55.9639C53.5264 9.90921 52.5978 11.5021 52.5978 13.7208V20.8888H48.5064V6.26832H52.2206L52.5978 8.45854C53.4103 7.1501 54.571 6.26832 56.8924 6.26832Z\"\n fill=\"#00EC97\"\n />\n <path\n d=\"M60.7221 23.961H59.2422L67.4542 0.124512H68.9341L60.7221 23.961ZM82.8081 8.08896V0.977843H86.8996V20.889H83.3304L82.9242 18.8694C81.9376 20.121 80.4867 21.0596 78.3394 21.0596C74.335 21.0596 71.4042 18.1867 71.4042 13.5503C71.4042 9.02762 74.335 6.09785 78.3104 6.09785C80.3706 6.09785 81.8505 6.89429 82.8081 8.08896ZM79.239 17.7885C81.4733 17.7885 82.8662 16.0818 82.8662 13.6072C82.8662 11.1041 81.4733 9.36896 79.239 9.36896C77.0046 9.36896 75.5827 11.0756 75.5827 13.5787C75.5827 16.0818 77.0046 17.7885 79.239 17.7885ZM96.2521 21.0596C91.2901 21.0596 88.3013 18.1298 88.3013 13.6072C88.3013 9.05607 91.3482 6.09785 95.962 6.09785C100.431 6.09785 103.449 8.82851 103.507 13.1236C103.507 13.5503 103.478 14.0338 103.39 14.489H92.5669V14.6881C92.6539 16.7645 94.0178 18.0161 96.078 18.0161C97.732 18.0161 98.8927 17.305 99.2409 15.9396H103.274C102.81 18.7841 100.228 21.0596 96.2521 21.0596ZM92.6539 11.8721H99.386C99.0959 10.0801 97.8481 9.08451 95.991 9.08451C94.2209 9.08451 92.8861 10.137 92.6539 11.8721ZM108.081 20.889L102.713 6.26851H107.094L110.692 16.793L114.233 6.26851H118.527L113.159 20.889H108.081ZM120.906 23.961H119.427L127.639 0.124512H129.118L120.906 23.961ZM140.671 6.09785C143.979 6.09785 146.707 7.83296 146.707 12.7823V20.889H142.615V13.1236C142.615 10.7343 141.629 9.3974 139.597 9.3974C137.508 9.3974 136.26 10.8765 136.26 13.3796V20.889H132.169V0.977843H136.26V8.06051C137.218 6.92273 138.553 6.09785 140.671 6.09785ZM158.367 13.5787V6.26851H162.459V20.889H158.832L158.454 19.1254C157.497 20.2632 156.191 21.0596 154.073 21.0596C150.997 21.0596 148.153 19.5521 148.153 14.3752V6.26851H152.245V13.8347C152.245 16.4516 153.115 17.7316 155.146 17.7316C157.178 17.7316 158.367 16.281 158.367 13.5787ZM173.022 6.09785C177.027 6.09785 179.928 8.91385 179.928 13.5503C179.928 18.073 177.027 21.0596 172.993 21.0596C170.846 21.0596 169.366 20.1494 168.408 18.8978L168.002 20.889H164.433V0.977843H168.524V8.1174C169.511 6.95118 170.962 6.09785 173.022 6.09785ZM172.094 17.7885C174.328 17.7885 175.779 16.0818 175.779 13.5787C175.779 11.0756 174.328 9.36896 172.094 9.36896C169.859 9.36896 168.466 11.0756 168.466 13.5503C168.466 16.0534 169.859 17.7885 172.094 17.7885Z\"\n fill=\"#151515\"\n />\n </g>\n <defs>\n <clipPath id=\"clip0_530_29\">\n <rect width=\"180\" height=\"24\" fill=\"white\" />\n </clipPath>\n </defs>\n </svg>\n </Link>\n </Wrapper>\n );\n};\n\nconst ProfileIcon = () => {\n const Wrapper = styled.svg`\n padding: 0.25rem;\n @media screen and (max-width: 768px) {\n display: none;\n }\n `;\n return (\n <Link\n to={linkHref({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"profile\", accountId: context.accountId },\n })}\n >\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.ProfileCard\"\n props={{ iconOnly: true, accountId: context.accountId || null }}\n />\n </Link>\n );\n};\n\nconst MenuIcon = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n style={{ height: 20, width: 20 }}\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M2 12.2986H14V13.3732H2V12.2986ZM2 9.07471H14V10.1493H2V9.07471ZM2 5.85083H14V6.92546H2V5.85083ZM2 2.62695H14V3.70158H2V2.62695Z\"\n fill=\"#818181\"\n />\n </svg>\n);\n\nconst Navbar = styled.div`\n padding: 1.5rem 0rem;\n\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n\n background: #f4f4f4;\n\n @media screen and (max-width: 768px) {\n padding: 1.875rem 1.375rem;\n }\n`;\n\nconst LinksContainer = styled.div`\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 1.5rem;\n\n @media screen and (max-width: 768px) {\n display: none;\n }\n`;\n\nconst MobileMenu = styled.button`\n all: unset;\n display: none;\n\n @media screen and (max-width: 768px) {\n display: block;\n }\n`;\n\nlet links = [\n {\n title: \"/feed\",\n href: \"announcements\",\n links: [],\n },\n {\n title: \"/communities\",\n href: \"communities\",\n links: [],\n },\n {\n title: \"/proposals\",\n href: \"proposals\",\n links: [],\n },\n {\n title: \"/about\",\n links: [\n { title: \"mission\", href: \"about\" },\n { title: \"blog\", href: \"blog\" },\n { title: \"newsletter\", href: \"https://newsletter.neardevhub.org\" },\n {\n title: \"calendar\",\n href: \"https://calendar.google.com/calendar/embed?height=600&wkst=2&bgcolor=%23ffffff&ctz=UTC&title&showNav=1&showDate=1&mode=AGENDA&showPrint=0&src=Y19mNTRlZDM3ZmQ5MjMyN2FjZGM3ZTQzNDNmZTQwNzIyYWU1Nzk3YjZjODI5MjliYTkzZTlmM2E4OWM2OTY1N2FiQGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20&color=%23616161\",\n },\n {\n title: \"brand kit\",\n href: \"https://drive.google.com/drive/folders/1C0GMmGq3MzbVPpxvf9807IU-7kpc2_v5?usp=sharing\",\n },\n ],\n },\n];\n\nif (hasModerator) {\n const isDevHubModerator = hasModerator({\n account_id: context.accountId,\n });\n\n if (isDevHubModerator) {\n links = [\n {\n title: \"/admin\",\n href: \"admin\",\n links: [],\n },\n ...links,\n ];\n }\n}\n\nconst MobileNav = styled.div`\n display: none;\n\n @media screen and (max-width: 768px) {\n display: flex;\n }\n\n position: absolute;\n top: 0;\n right: 0;\n\n width: 207px;\n\n padding: 24px 36px 36px 16px;\n flex-direction: column;\n align-items: flex-end;\n gap: 2.5rem;\n flex-shrink: 0;\n\n border-radius: 0px 0px 0px 16px;\n background: rgba(41, 41, 41, 0.6);\n backdrop-filter: blur(5px);\n\n z-index: 50;\n`;\n\nconst MobileLink = styled.a`\n color: #f4f4f4 !important;\n font-size: 20px;\n font-style: normal;\n font-weight: 400;\n line-height: 20px; /* 100% */\n margin-bottom: 1rem;\n\n &.active {\n color: #00ec97 !important;\n }\n\n &:hover {\n text-decoration: none;\n color: #00ec97 !important;\n }\n`;\n\nreturn (\n <Navbar className=\"position-relative\">\n <Logo />\n <div className=\"d-flex gap-3 align-items-center\">\n <LinksContainer>\n {links.map((link) => (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.NavbarDropdown\"\n props={{\n title: link.title,\n href: link.href,\n links: link.links,\n page: page,\n }}\n />\n ))}\n </LinksContainer>\n {context.accountId && <ProfileIcon />}\n <MobileMenu onClick={() => setShowMenu(!showMenu)}>\n <MenuIcon />\n </MobileMenu>\n </div>\n {showMenu && (\n <MobileNav>\n <div\n onClick={() => setShowMenu(!showMenu)}\n style={{ cursor: \"pointer\" }}\n >\n <i className=\"bi bi-x\" style={{ fontSize: 20, color: \"#F4F4F4\" }}></i>\n </div>\n <div className=\"d-flex flex-column gap-2\">\n {links.map((link, idx) =>\n link.href ? (\n <MobileLink\n key={`mobile-link-${idx}`}\n className={link.href === props.page && \"active\"}\n href={`/devhub.megha19.near/widget/app?page=${link.href}`}\n >\n {link.title}\n </MobileLink>\n ) : (\n link.links.map((it, idx) =>\n it.href.startsWith(\"http://\") ||\n it.href.startsWith(\"https://\") ? (\n <MobileLink\n key={`nested-link-${idx}`}\n className={link.href === props.page && \"active\"}\n href={it.href}\n target=\"no_blank\"\n >\n /{it.title}\n </MobileLink>\n ) : (\n <MobileLink\n key={`nested-link-${idx}`}\n className={link.href === props.page && \"active\"}\n href={`/devhub.megha19.near/widget/app?page=${it.href}`}\n >\n /{it.title}\n </MobileLink>\n )\n )\n )\n )}\n </div>\n </MobileNav>\n )}\n </Navbar>\n);\n" }, "devhub.entity.community.configuration.AboutConfigurator": { "": "const CommunityAboutSchema = {\n bio_markdown: {\n format: \"markdown\",\n\n inputProps: {\n min: 3,\n max: 200,\n\n placeholder:\n \"Tell people about your community. This will appear on your community’s homepage.\",\n required: true,\n resize: \"none\",\n },\n\n label: \"Bio\",\n multiline: true,\n order: 1,\n },\n\n twitter_handle: {\n inputProps: { prefix: \"https://twitter.com/\", min: 2, max: 60 },\n label: \"Twitter\",\n order: 2,\n },\n\n github_handle: {\n inputProps: { prefix: \"https://github.com/\", min: 2, max: 60 },\n label: \"Github\",\n order: 3,\n },\n\n telegram_handle: {\n inputProps: { prefix: \"https://t.me/\", min: 2, max: 60 },\n format: \"comma-separated\",\n label: \"Telegram\",\n order: 4,\n },\n\n website_url: {\n inputProps: { prefix: \"https://\", min: 2, max: 60, validUrl: true },\n label: \"Website\",\n order: 5,\n },\n};\n\nconst { data, onSubmit, onCancel, setIsActive, isActive } = props;\n\nfunction handleOnSubmit(v) {\n onSubmit(v);\n setIsActive(false);\n}\n\nreturn (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.organism.Configurator\"}\n props={{\n externalState: data,\n schema: CommunityAboutSchema,\n onSubmit: handleOnSubmit,\n isActive,\n onCancel: onCancel,\n }}\n />\n);\n" }, "devhub.feature.post-search.by-tag": { "": "const { getAllLabels } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nif (!getAllLabels) {\n return <p>Loading modules...</p>;\n}\n\nconst selectedTags = props.tag ? [{ name: props.tag }] : [];\n\nconst tags = getAllLabels();\n\nif (tags === null) {\n return <div>Loading ...</div>;\n}\n\nconst onChange = (selectedTags) =>\n props.onTagSearch?.(selectedTags[0]?.name ?? \"\");\n\nreturn (\n <Typeahead\n clearButton\n id=\"basic-typeahead-single\"\n labelKey=\"name\"\n options={tags.map((tag) => ({ name: tag }))}\n placeholder=\"Search by tag\"\n selected={selectedTags}\n {...{ onChange }}\n />\n);\n" }, "devhub.entity.addon.github.Configurator": { "": "const Struct = VM.require(\"devhub.megha19.near/widget/core.lib.struct\");\n\nif (!Struct) {\n return <p>Loading modules...</p>;\n}\nconst { useQuery } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\nconst { uuid, withUUIDIndex } = VM.require(\n \"devhub.megha19.near/widget/core.lib.uuid\"\n);\n\nuuid || (uuid = () => {});\nwithUUIDIndex || (withUUIDIndex = () => {});\nuseQuery || (useQuery = () => {});\n\nconst AttractableDiv = styled.div`\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\nconst settings = {\n maxColumnsNumber: 20,\n};\n\nconst GithubKanbanBoardTicketFeaturesSchema = {\n id: { label: \"GitHub ID\" },\n author: { label: \"Author\" },\n labels: { label: \"Labels\" },\n type: { label: \"Type\" },\n};\n\nconst GithubKanbanBoardTicketTypesSchema = {\n issue: { label: \"Issue\" },\n pullRequest: { label: \"Pull Request\" },\n};\n\nconst GithubKanbanBoardLabelsSchema = {\n allLabelsMust: { label: \"All labels must be present in ticket\" },\n};\n\nconst GithubKanbanBoardDefaults = {\n columns: {\n ...withUUIDIndex({\n description: \"\",\n labelSearchTerms: [],\n title: \"\",\n allLabelsMust: false,\n }),\n },\n dataTypesIncluded: { issue: true, pullRequest: true },\n description: \"\",\n repoURL: \"\",\n ticketState: \"all\",\n title: \"\",\n metadata: {\n id: uuid(),\n type: \"github.kanban_board\",\n ticket: {\n type: \"github.kanban_ticket\",\n features: { id: true, author: true, labels: true, type: true },\n },\n },\n};\n\nconst toMigrated = ({ metadata, id, ...restParams }) => ({\n ...GithubKanbanBoardDefaults,\n metadata: {\n ...GithubKanbanBoardDefaults.metadata,\n ...metadata,\n id: id ?? metadata.id,\n },\n ...restParams,\n});\n\nfunction extractOwnerAndRepo(url) {\n // Remove any leading or trailing slashes and split the URL by \"/\"\n const parts = url\n .trim()\n .replace(/^\\/+|\\/+$/g, \"\")\n .split(\"/\");\n\n // Check if the URL matches the GitHub repository format\n if (parts.length === 5 && parts[2] === \"github.com\") {\n const owner = parts[3];\n const repo = parts[4];\n return { owner, repo };\n } else {\n return null;\n }\n}\n\nfunction isValidGitHubRepoLink(url) {\n // Regular expression to match GitHub repository URLs\n const githubRepoRegex =\n /^(?:https?:\\/\\/)?(?:www\\.)?github\\.com\\/([^\\/]+)\\/([^\\/]+)\\/?$/;\n\n // Check if the URL matches the GitHub repository format\n return githubRepoRegex.test(url);\n}\n\nconst GithubViewConfigurator = ({ kanbanBoards, permissions, onSubmit }) => {\n const data = kanbanBoards ? Object.values(kanbanBoards)?.[0] : {};\n\n if (!data) {\n return (\n <div class=\"alert alert-danger\" role=\"alert\">\n Loading...\n </div>\n );\n }\n\n const initialBoardState = Struct.typeMatch(data)\n ? toMigrated(data)\n : GithubKanbanBoardDefaults;\n\n const getColumnData = useCallback((state) => {\n if (Object.keys(state).length > 0) {\n return state?.columns ?? {};\n }\n return state;\n }, []);\n\n const getNonColumnData = useCallback((state) => {\n if (Object.keys(state).length > 0) {\n delete state.columns;\n return state;\n }\n return state;\n }, []);\n\n // to improve the state update speed, decoupled columns and other configuration metadata\n const [parentState, setParentState] = useState(initialBoardState);\n const [metadataState, setMetadata] = useState(\n getNonColumnData(initialBoardState)\n );\n const [showPreview, setPreview] = useState(false);\n const [columnsState, setColumnsState] = useState(\n getColumnData(initialBoardState)\n );\n const [repoLabels, setRepoLabels] = useState([]);\n\n function fetchLabelsFromRepo(url) {\n const data = extractOwnerAndRepo(url);\n if (data) {\n const { repo, owner } = data;\n useCache(\n () =>\n asyncFetch(\n `https://api.github.com/repos/${owner}/${repo}/labels`\n ).then((res) => {\n if (Array.isArray(res.body)) {\n const labels = [];\n res.body.map((item) => {\n labels.push(item.name);\n });\n setRepoLabels(labels);\n }\n }),\n owner + repo + \"labels\",\n { subscribe: false }\n );\n }\n }\n\n useEffect(() => {\n if (metadataState.repoURL && isValidGitHubRepoLink(metadataState.repoURL)) {\n fetchLabelsFromRepo(metadataState.repoURL);\n }\n }, [metadataState]);\n\n const formUpdate =\n ({ path, via: customFieldUpdate, isColumnsUpdate, ...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 (isColumnsUpdate ? { columns: columnsState } : metadataState) ?? {},\n path,\n (node) => transformFn(node)\n );\n if (isColumnsUpdate) {\n setColumnsState(updatedValues?.columns);\n } else {\n setMetadata((prevFormState) => ({\n ...prevFormState,\n ...updatedValues,\n }));\n }\n };\n\n const formReset = () => {\n setColumnsState(getColumnData(initialBoardState));\n setMetadata(getNonColumnData(initialBoardState));\n setParentState(initialBoardState);\n };\n\n const columnsCreateNew = ({ lastKnownValue }) =>\n Object.keys(lastKnownValue).length < settings.maxColumnsNumber\n ? {\n ...(lastKnownValue ?? {}),\n ...withUUIDIndex({\n description: \"\",\n labelSearchTerms: [],\n title: \"\",\n allLabelsMust: false,\n }),\n }\n : lastKnownValue;\n\n const columnsDeleteById =\n (id) =>\n ({ lastKnownValue }) =>\n Object.fromEntries(\n Object.entries(lastKnownValue).filter(([columnId]) => columnId !== id)\n );\n\n const onCancel = () => {\n formReset();\n };\n\n const updateParentState = () => {\n const updatedState = { ...metadataState, columns: columnsState };\n setParentState(updatedState);\n return updatedState;\n };\n\n const onSave = () => onSubmit(updateParentState());\n\n const formElement = (\n <>\n <div className=\"d-flex flex-column\">\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"w-100\",\n key: `${metadataState.metadata.id}-repoURL`,\n label: \"Repository URL\",\n onChange: formUpdate({\n path: [\"repoURL\"],\n isColumnsUpdate: false,\n }),\n placeholder: \"https://github.com/example-org/example-repo\",\n value: metadataState.repoURL ?? \"\",\n }}\n />\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"w-100\",\n key: `${metadataState.metadata.id}-title`,\n label: \"Title\",\n onChange: formUpdate({ path: [\"title\"], isColumnsUpdate: false }),\n placeholder: \"NEAR Protocol NEPs\",\n value: metadataState.title ?? \"\",\n }}\n />\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"w-100\",\n key: `${metadataState.metadata.id}-description`,\n label: \"Description\",\n onChange: formUpdate({\n path: [\"description\"],\n isColumnsUpdate: false,\n }),\n placeholder: \"Latest NEAR Enhancement Proposals by status.\",\n value: metadataState.description ?? \"\",\n }}\n />\n </div>\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2\">\n <label>Select which tasks you want to display:</label>\n <div className=\"input-group\" style={{ width: \"fit-content\" }}>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.organism.Configurator`}\n props={{\n heading: \"Ticket types\",\n classNames: { root: \"col-12 col-md-4 h-auto\" },\n externalState: metadataState.dataTypesIncluded,\n isActive: true,\n isEmbedded: true,\n isUnlocked: permissions.can_configure,\n onChange: formUpdate({\n path: [\"dataTypesIncluded\"],\n isColumnsUpdate: false,\n }),\n schema: GithubKanbanBoardTicketTypesSchema,\n hideSubmitBtn: true,\n }}\n />\n </div>\n </div>\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2\">\n <label>Select which state of tickets you want to display:</label>\n <div className=\"input-group mt-2\">\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Switch`}\n props={{\n currentValue: metadataState.ticketState,\n key: \"ticketState\",\n onChange: formUpdate({\n path: [\"ticketState\"],\n isColumnsUpdate: false,\n }),\n options: [\n { label: \"All\", value: \"all\" },\n { label: \"Open\", value: \"open\" },\n { label: \"Closed\", value: \"closed\" },\n ],\n }}\n />\n </div>\n </div>\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2\">\n <label>\n Select which items you want to display on each card in a column:\n </label>\n <div className=\"input-group\" style={{ width: \"fit-content\" }}>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.organism.Configurator`}\n props={{\n heading: \"Card fields\",\n classNames: { root: \"col-12 col-md-4 h-auto\" },\n externalState: metadataState.metadata.ticket.features,\n isActive: true,\n isEmbedded: true,\n isUnlocked: permissions.can_configure,\n onChange: formUpdate({\n path: [\"metadata\", \"ticket\", \"features\"],\n isColumnsUpdate: false,\n }),\n schema: GithubKanbanBoardTicketFeaturesSchema,\n hideSubmitBtn: true,\n }}\n />\n </div>\n </div>\n\n <div className=\"d-flex align-items-center justify-content-between mb-2\">\n <span className=\"d-inline-flex gap-2 m-0\">\n <i className=\"bi bi-list-task\" />\n <span>{`Each board configuration ( maximum allowed - ${settings.maxColumnsNumber} ) : `}</span>\n </span>\n </div>\n\n <div className=\"d-flex flex-column align-items-center gap-3 w-100 boardconfiguration\">\n {Object.values(columnsState ?? {}).map(\n (\n { id, description, labelSearchTerms, title, allLabelsMust },\n index\n ) => (\n <AttractableDiv\n className=\"d-flex gap-3 rounded-4 border p-3 w-100\"\n key={`column-${id}-configurator`}\n >\n <div className=\"d-flex flex-column gap-1 w-100\">\n <div>Board #{index}</div>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n key: `${metadataState.metadata.id}-column-${id}-title`,\n label: \"Title\",\n onChange: formUpdate({\n path: [\"columns\", id, \"title\"],\n isColumnsUpdate: true,\n }),\n placeholder: \"👀 Review\",\n value: title,\n }}\n />\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2\">\n <label>Search tickets using labels:</label>\n <div className=\"input-group\">\n <Typeahead\n id=\"hashtags\"\n onChange={(data) => {\n const formUpdateFunc = formUpdate({\n path: [\"columns\", id, \"labelSearchTerms\"],\n isColumnsUpdate: true,\n });\n return formUpdateFunc(data.join(\", \"));\n }}\n selected={labelSearchTerms?.[0] ? labelSearchTerms : []}\n multiple\n labelKey=\"hashtags\"\n emptyLabel=\"Find your unique label\"\n placeholder=\"WG-, draft, review, proposal,\"\n options={repoLabels}\n />\n </div>\n </div>\n <div style={{ width: \"fit-content\" }}>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.organism.Configurator`}\n props={{\n heading: \"\",\n classNames: { root: \"col-12 col-md-4 h-auto\" },\n externalState: { allLabelsMust: allLabelsMust },\n isActive: true,\n isEmbedded: true,\n isUnlocked: permissions.can_configure,\n onChange: (data) => {\n const formUpdateFunc = formUpdate({\n path: [\"columns\", id, \"allLabelsMust\"],\n isColumnsUpdate: true,\n });\n return formUpdateFunc(data[\"allLabelsMust\"]);\n },\n schema: GithubKanbanBoardLabelsSchema,\n hideSubmitBtn: true,\n }}\n />\n </div>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n key: `${metadataState.metadata.id}-column-${id}-description`,\n label: \"Description\",\n onChange: formUpdate({\n path: [\"columns\", id, \"description\"],\n isColumnsUpdate: true,\n }),\n placeholder:\n \"NEPs that need a review by Subject Matter Experts.\",\n value: description,\n }}\n />\n </div>\n\n <div\n className=\"d-flex flex-column gap-3 border-start p-3 pe-0\"\n style={{ marginTop: -16, marginBottom: -16 }}\n >\n <button\n className=\"btn btn-outline-danger\"\n onClick={formUpdate({\n path: [\"columns\"],\n via: columnsDeleteById(id),\n isColumnsUpdate: true,\n })}\n title=\"Delete column\"\n >\n <i className=\"bi bi-trash-fill\" />\n </button>\n </div>\n </AttractableDiv>\n )\n )}\n </div>\n </>\n );\n\n return (\n <div\n className=\"d-flex flex-column gap-4 w-100\"\n style={{ maxWidth: \"100%\" }}\n >\n <ul className=\"nav nav-tabs\" id=\"editPreviewTabs\" role=\"tablist\">\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className={`nav-link ${!showPreview ? \"active\" : \"\"}`}\n id=\"edit-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#edit\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"edit\"\n aria-selected=\"true\"\n onClick={() => setPreview(false)}\n >\n Edit\n </button>\n </li>\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className={`nav-link ${showPreview ? \"active\" : \"\"}`}\n id=\"preview-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#preview\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"preview\"\n aria-selected=\"false\"\n onClick={() => {\n updateParentState();\n setPreview(true);\n }}\n >\n Preview\n </button>\n </li>\n </ul>\n {showPreview ? (\n <div>\n <Widget\n src={`devhub.megha19.near/widget/devhub.entity.addon.github.Viewer`}\n props={{\n kanbanBoards: {\n [parentState.metadata.id]: parentState,\n },\n }}\n />\n </div>\n ) : (\n <div className={\"d-flex flex-column gap-4 w-100\"}>\n <div className={\"d-flex flex-column gap-2 w-100\"}>\n <div className=\"d-flex align-items-center justify-content-between gap-3 w-100\">\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n <i className=\"bi bi-gear-wide-connected\" />\n <span>GitHub board configuration</span>\n </h5>\n </div>\n <div>\n This configuration enables integration of your GitHub repository\n as a Kanban board, facilitating issue and pull request tracking.\n You can create distinct columns to organize various items, each\n with unique labels.\n </div>\n </div>\n {Object.keys(parentState).length > 0 && (\n <div>\n {formElement}\n <div className=\"d-flex justify-content-between gap-2 mt-4\">\n <div style={{ minWidth: \"200px\" }}>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"btn-sm btn-outline-secondary\",\n },\n label: \"Add another column\",\n disabled:\n parentState.columns &&\n Object.keys(parentState.columns).length >=\n settings.maxColumnsNumber,\n icon: { type: \"bootstrap_icon\", variant: \"bi-plus-lg\" },\n onClick: formUpdate({\n path: [\"columns\"],\n via: columnsCreateNew,\n isColumnsUpdate: true,\n }),\n }}\n />\n </div>\n <div className=\"d-flex gap-3 justify-content-end w-100\">\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"d-flex btn btn-outline-danger shadow-none border-0\",\n },\n isHidden: typeof onCancel !== \"function\",\n label: \"Cancel\",\n onClick: onCancel,\n }}\n />\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: { root: \"btn btn-success\" },\n icon: {\n type: \"svg_icon\",\n variant: \"floppy_drive\",\n width: 14,\n height: 14,\n },\n isHidden: typeof onSave !== \"function\",\n label: \"Save\",\n onClick: onSave,\n }}\n />\n </div>\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n );\n};\n\nreturn GithubViewConfigurator(props);\n" }, "devhub.feature.proposal-search.by-sort": { "": "const options = [\n { label: \"Most recent\", value: \"proposal_id\" }, // proposal_id desc\n { label: \"Most viewed\", value: \"views\" }, // views desc\n // TODO add track_comments function to devhub to track it in the indexer\n // { label: \"Most commented\", value: \"\" }, // comments desc\n // { label: \"Unanswered\", value: \"\" }, // where comments = 0\n { label: \"None\", value: \"\" }, // where comments = 0\n];\n\nconst setSelected = props.onStateChange ?? (() => {});\n\nreturn (\n <div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: options,\n label: \"Sort\",\n selectedValue: options[0],\n onUpdate: (v) => {\n setSelected(v);\n },\n }}\n />\n </div>\n);\n" }, "devhub.entity.addon.blog.editor.layout": { "": "const { Sidebar, Content, getData, editData } = props;\n\nconst [selectedItem, setSelectedItem] = useState(editData);\n\nconst handleItemClick = (item) => {\n if (item) {\n getData(item).then((item) => {\n setSelectedItem(item);\n });\n } else {\n setSelectedItem(null);\n }\n};\n\nreturn (\n <div\n className=\"template\"\n style={{ display: \"flex\", width: \"100%\", height: \"100%\" }}\n >\n <div\n className=\"left-panel\"\n style={{\n margin: \"20px 20px 80px 20px\",\n }}\n >\n <Sidebar selectedItem={selectedItem} handleItemClick={handleItemClick} />\n </div>\n <div\n className=\"right-panel\"\n style={{ flex: 1, width: 0, overflow: \"scroll\" }}\n key={selectedItem.id}\n >\n <Content data={selectedItem} />\n </div>\n </div>\n);\n" }, "devhub.entity.post.draft": { "": "const DRAFT_STATE_STORAGE_KEY = \"POST_DRAFT_STATE\";\n\nconst onDraftStateChange = (draftState) =>\n Storage.privateSet(DRAFT_STATE_STORAGE_KEY, JSON.stringify(draftState));\nlet draftState;\ntry {\n draftState = JSON.parse(Storage.privateGet(DRAFT_STATE_STORAGE_KEY));\n} catch (e) {}\n\nreturn { DRAFT_STATE_STORAGE_KEY, draftState, onDraftStateChange };\n" }, "devhub.components.atom.Tag": { "": "const black = props.black;\n\nconst Span = styled.span`\n color: ${black ? \"#818181\" : \"#00ec97\"};\n font-size: 16px;\n font-style: normal;\n font-weight: 700;\n line-height: 20px; /* 125% */\n`;\n\nconst Tag = ({ tag }) => <Span>{tag}</Span>;\n\nreturn Tag(props);\n" }, "devhub.entity.addon.blog.editor.sidebar": { "": "const { data, editPostId, handleItemClick, selectedItem } = props;\n\nconst SidebarContainer = styled.div`\n background-color: #f0f0f0;\n padding: 16px;\n width: 200px;\n display: flex;\n flex-direction: column;\n align-items: center;\n height: 100%;\n gap: 4px;\n`;\n\nconst SidebarButton = styled.button`\n display: flex;\n padding: 14px 16px;\n text-align: center;\n cursor: pointer;\n gap: 16px;\n width: 100%;\n\n border-radius: 4px;\n border: 1px solid #00ec97;\n\n background-color: ${({ selected }) => (selected ? \"#00ec97\" : \"white\")};\n color: ${({ selected }) => (selected ? \"white\" : \"black\")};\n`;\n\nreturn (\n <SidebarContainer id=\"edit-blog-sidebar\">\n <p>Blog posts</p>\n <SidebarButton\n selected={!selectedItem.id}\n onClick={() => handleItemClick(null)}\n id=\"create-new-blog\"\n >\n New\n </SidebarButton>\n {(data || []).map((it) => (\n <SidebarButton\n id={`edit-blog-selector-${it.post_id}`}\n key={it.post_id}\n selected={parseInt(selectedItem.id) === it.post_id}\n onClick={() => handleItemClick(it.post_id)}\n >\n Id: {it.post_id}\n </SidebarButton>\n ))}\n </SidebarContainer>\n);\n" }, "devhub.feature.proposal-search.by-author": { "": "const [authorsOptions, setAuthorsOptions] = useState([]);\nconst [selectedAuthor, setSelectedAuthor] = useState(null);\n\nif (!authorsOptions.length) {\n const data = [{ label: \"None\", value: \"\" }];\n const authors = Near.view(\n \"devhub.near\",\n \"get_all_proposal_authors\",\n {}\n );\n\n if (Array.isArray(authors)) {\n for (const author of authors) {\n data.push({ label: author, value: author });\n }\n setAuthorsOptions(data);\n }\n}\n\nconst Container = styled.div`\n .dropdown-menu {\n max-height: 400px;\n overflow-x: auto;\n }\n`;\nreturn (\n <Container>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: authorsOptions,\n label: \"Author\",\n onUpdate: (v) => {\n setSelectedAuthor(v);\n props.onAuthorChange(v);\n },\n selectedValue: props.author,\n }}\n />\n </Container>\n);\n" }, "devhub.page.announcements": { "": "const MainContent = styled.div`\n padding-left: 2rem;\n padding-right: 2rem;\n flex: 3;\n @media screen and (max-width: 960px) {\n padding-left: 0rem;\n padding-right: 0rem;\n }\n .post:hover {\n background-color: inherit !important;\n }\n`;\n\nconst Heading = styled.div`\n font-size: 28px;\n font-weight: 600;\n margin-bottom: 1rem;\n`;\n\nconst SubHeading = styled.div`\n font-size: 15px;\n font-weight: 600;\n`;\n\nconst Container = styled.div`\n flex-wrap: no-wrap;\n max-width: 100%;\n\n background: #fff;\n\n .max-width-100 {\n max-width: 100%;\n }\n @media screen and (max-width: 960px) {\n flex-wrap: wrap;\n }\n\n .card {\n border-radius: 1rem !important;\n }\n\n .display-none {\n display: none;\n }\n`;\n\nconst TabContainer = styled.div`\n display: flex;\n gap: 5px;\n background-color: rgb(244, 244, 244);\n justify-content: center;\n align-items: center;\n border-radius: 5px;\n padding: 0 3px;\n`;\n\nconst Tab = styled.div`\n cursor: pointer;\n font-size: 14px;\n font-weight: 700;\n color: rgb(153, 153, 153);\n padding: 5px 10px;\n border-radius: 5px;\n`;\n\nconst Line = styled.div`\n width: 100%;\n border-bottom: 1px solid rgb(223, 223, 223);\n`;\n\nconst tabs = [\"All\", \"Announcements\", \"Discussions\"];\nconst [selectedTab, setSelectedTab] = useState(\"All\");\nconst [sort, setSort] = useState(\"desc\");\n\nconst followGraph = Social.keys(\n `community.devhub.near/graph/follow/*`,\n \"final\"\n);\nconst accountsFollowing =\n props.accountsFollowing ??\n (followGraph\n ? Object.keys(followGraph[\"community.devhub.near\"].graph.follow || {})\n : null);\n\nconst filteredAccountIds =\n accountsFollowing &&\n accountsFollowing.filter((account) => {\n if (selectedTab === \"All\") return true;\n if (selectedTab === \"Announcements\") {\n return !account.includes(\"discussions\");\n }\n if (selectedTab === \"Discussions\") {\n return account.includes(\"discussions\");\n }\n });\n\nreturn (\n <div className=\"w-100\" style={{ maxWidth: \"100%\" }}>\n <Heading>Activity Feed</Heading>\n <Line />\n <Container className=\"d-flex gap-3 px-2 py-4\">\n <MainContent className=\"max-width-100\">\n <div className=\"d-flex flex-column gap-4\">\n <div className=\"d-flex flex-wrap justify-content-between\">\n <TabContainer>\n {tabs.map((tab) => (\n <Tab\n className={selectedTab === tab ? \"text-black bg-white\" : \"\"}\n onClick={() => setSelectedTab(tab)}\n >\n {tab}\n </Tab>\n ))}\n </TabContainer>\n <div className={\"d-flex align-items-center gap-2\"}>\n <select\n name=\"sort\"\n id=\"sort\"\n class=\"form-select border-0\"\n value={sort}\n onChange={(e) => {\n setSort(e.target.value);\n }}\n >\n <option selected value=\"desc\">\n Latest\n </option>\n <option value=\"recentcommentdesc\">Last Commented</option>\n </select>\n </div>\n </div>\n\n <div\n className={\"card p-4\"}\n style={{ overflow: \"auto\", height: \"60vh\" }}\n >\n <Widget\n key=\"feed\"\n src=\"devhub.megha19.near/widget/devhub.components.feed.SubscribedFeed\"\n props={{\n sort: sort,\n accounts: filteredAccountIds,\n threshold: 250,\n }}\n />\n </div>\n </div>\n </MainContent>\n </Container>\n </div>\n);\n" }, "devhub.page.profile": { "": "const accountId = props.accountId || context.accountId;\n\nif (!accountId) {\n return \"No Account Logged In!\";\n}\n\nreturn (\n <div className=\"w-100 bg-white overflow-hidden px-3\">\n <Widget src=\"mob.near/widget/ProfilePage\" props={{ accountId }} />\n </div>\n);\n" }, "devhub.components.molecule.MarkdownEditor": { "": "const MarkdownEditor = ({ data, onChange, showAutoComplete }) => {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.SimpleMDE\"}\n props={{\n data,\n onChange,\n showAutoComplete,\n }}\n />\n );\n};\n\nreturn MarkdownEditor(props);\n" }, "devhub.entity.addon.github.Viewer": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\nconst { useQuery } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nuseQuery || (useQuery = () => {});\n\nhref || (href = () => {});\nconst { kanbanBoards, handle, permissions } = props;\n\nconst data = Object.values(kanbanBoards ?? {})?.[0];\n\nif (!kanbanBoards || !data?.metadata) {\n return (\n <div\n className=\"d-flex flex-column align-items-center justify-content-center gap-4\"\n style={{ height: 384 }}\n >\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n {permissions.can_configure\n ? \"You can configure the board by clicking on the settings icon.\"\n : \"This board isn't configured yet.\"}\n </h5>\n </div>\n );\n}\n\nreturn (\n <Widget\n src={`devhub.megha19.near/widget/devhub.entity.addon.${data.metadata.type}`}\n props={{\n ...data,\n isConfiguratorActive: false,\n isSynced: true,\n permissions,\n }}\n />\n);\n" }, "devhub.components.feed.MergedIndexFeed": { "": "if (!props.index) {\n return \"props.index is not defined\";\n}\nconst indices = JSON.parse(\n JSON.stringify(Array.isArray(props.index) ? props.index : [props.index])\n);\n\nconst filter = props.filter;\n\nconst renderItem =\n props.renderItem ??\n ((item) => (\n <div key={JSON.stringify(item)} style={{ minHeight: \"150px\" }}>\n #{item.blockHeight}: {JSON.stringify(item)}\n </div>\n ));\nconst cachedRenderItem = (item, i) => {\n const key = JSON.stringify(item);\n\n if (!(key in state.cachedItems)) {\n state.cachedItems[key] = renderItem(item, i);\n State.update();\n }\n return state.cachedItems[key];\n};\n\nconst initialRenderLimit = props.initialRenderLimit ?? 10;\nconst addDisplayCount = props.nextLimit ?? initialRenderLimit;\nconst reverse = !!props.reverse;\n\nconst computeFetchFrom = (items, limit, desc) => {\n if (!items || items.length < limit) {\n return false;\n }\n const blockHeight = items[items.length - 1].blockHeight;\n return desc ? blockHeight - 1 : blockHeight + 1;\n};\n\nconst mergeItems = (iIndex, oldItems, newItems, desc) => {\n const index = indices[iIndex];\n const items = [\n ...new Set(\n [\n ...newItems.map((item) => ({\n ...item,\n action: index.action,\n key: index.key,\n index: iIndex,\n })),\n ...oldItems,\n ].map((i) => JSON.stringify(i))\n ),\n ].map((i) => JSON.parse(i));\n items.sort((a, b) => a.blockHeight - b.blockHeight);\n if (desc) {\n items.reverse();\n }\n return items;\n};\n\nconst jIndices = JSON.stringify(indices);\nif (jIndices !== state.jIndices) {\n State.update({\n jIndices,\n feeds: indices.map(() => ({})),\n items: [],\n displayCount: initialRenderLimit,\n cachedItems: {},\n });\n}\n\nlet stateChanged = false;\nfor (let iIndex = 0; iIndex < indices.length; ++iIndex) {\n const index = indices[iIndex];\n const feed = state.feeds[iIndex];\n let feedChanged = false;\n index.options = index.options || {};\n index.options.limit = Math.min(\n Math.max(initialRenderLimit + addDisplayCount * 2, index.options.limit),\n 100\n );\n const desc = index.options.order === \"desc\";\n\n const initialItems = Social.index(\n index.action,\n index.key,\n index.options,\n index.cacheOptions\n );\n if (initialItems === null) {\n continue;\n }\n\n const jInitialItems = JSON.stringify(initialItems);\n const nextFetchFrom = computeFetchFrom(\n initialItems,\n index.options.limit,\n desc\n );\n if (feed.jInitialItems !== jInitialItems) {\n feed.jInitialItems = jInitialItems;\n feedChanged = true;\n if (nextFetchFrom !== feed.initialNextFetchFrom) {\n feed.fetchFrom = false;\n feed.items = mergeItems(iIndex, [], initialItems, desc);\n feed.initialNextFetchFrom = nextFetchFrom;\n feed.nextFetchFrom = nextFetchFrom;\n } else {\n feed.items = mergeItems(iIndex, feed.items, initialItems, desc);\n }\n }\n\n feed.usedCount = 0;\n\n if (feedChanged) {\n state.feeds[iIndex] = feed;\n stateChanged = true;\n }\n}\n\n// Construct merged feed and compute usage per feed.\n\nconst filteredItems = [];\nwhile (filteredItems.length < state.displayCount) {\n let bestItem = null;\n for (let iIndex = 0; iIndex < indices.length; ++iIndex) {\n const index = indices[iIndex];\n const feed = state.feeds[iIndex];\n const desc = index.options.order === \"desc\";\n if (!feed.items) {\n continue;\n }\n const item = feed.items[feed.usedCount];\n if (!item) {\n continue;\n }\n if (\n bestItem === null ||\n (desc\n ? item.blockHeight > bestItem.blockHeight\n : item.blockHeight < bestItem.blockHeight)\n ) {\n bestItem = item;\n }\n }\n if (!bestItem) {\n break;\n }\n state.feeds[bestItem.index].usedCount++;\n if (filter) {\n if (filter.ignore) {\n if (bestItem.accountId in filter.ignore) {\n continue;\n }\n }\n if (filter.require) {\n if (!(bestItem.accountId in filter.require)) {\n continue;\n }\n }\n }\n filteredItems.push(bestItem);\n}\n\n// Fetch new items for feeds that don't have enough items.\nfor (let iIndex = 0; iIndex < indices.length; ++iIndex) {\n const index = indices[iIndex];\n const feed = state.feeds[iIndex];\n const desc = index.options.order === \"desc\";\n let feedChanged = false;\n\n if (\n (feed.items.length || 0) - feed.usedCount < addDisplayCount * 2 &&\n !feed.fetchFrom &&\n feed.nextFetchFrom &&\n feed.nextFetchFrom !== feed.fetchFrom\n ) {\n feed.fetchFrom = feed.nextFetchFrom;\n feedChanged = true;\n }\n\n if (feed.fetchFrom) {\n const limit = addDisplayCount;\n const newItems = Social.index(\n index.action,\n index.key,\n Object.assign({}, index.options, {\n from: feed.fetchFrom,\n subscribe: undefined,\n limit,\n })\n );\n if (newItems !== null) {\n feed.items = mergeItems(iIndex, feed.items, newItems, desc);\n feed.fetchFrom = false;\n feed.nextFetchFrom = computeFetchFrom(newItems, limit, desc);\n feedChanged = true;\n }\n }\n\n if (feedChanged) {\n state.feeds[iIndex] = feed;\n stateChanged = true;\n }\n}\n\nif (stateChanged) {\n State.update();\n}\n\nconst makeMoreItems = () => {\n State.update({\n displayCount: state.displayCount + addDisplayCount,\n });\n};\n\nconst loader = (\n <div className=\"loader\" key={\"loader\"}>\n <span\n className=\"spinner-grow spinner-grow-sm me-1\"\n role=\"status\"\n aria-hidden=\"true\"\n />\n Loading ...\n </div>\n);\n\nconst fetchMore =\n props.manual &&\n (state.feeds.some((f) => !!f.fetchFrom) &&\n filteredItems.length < state.displayCount\n ? loader\n : state.displayCount < filteredItems.length && (\n <div key={\"loader more\"}>\n <a href=\"javascript:void\" onClick={(e) => makeMoreItems()}>\n {props.loadMoreText ?? \"Load more...\"}\n </a>\n </div>\n ));\n\nconst items = filteredItems ? filteredItems.slice(0, state.displayCount) : [];\nif (reverse) {\n items.reverse();\n}\n\nconst renderedItems = items.map(cachedRenderItem);\n\nreturn props.manual ? (\n <>\n {reverse && fetchMore}\n {renderedItems}\n {!reverse && fetchMore}\n </>\n) : (\n <InfiniteScroll\n pageStart={0}\n loadMore={makeMoreItems}\n threshold={props.threshold ?? 250}\n hasMore={state.displayCount <= filteredItems.length}\n loader={loader}\n useWindow={false}\n >\n {renderedItems}\n </InfiniteScroll>\n);\n" }, "devhub.entity.addon.github.kanban_ticket": { "": "const AttractableDiv = styled.div`\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\nconst ticketStates = {\n closed: { displayName: \"Closed\", icon: \"bi-lock-fill\" },\n open: { displayName: \"Open\", icon: \"bi-unlock-fill\" },\n};\n\nconst ticketTypes = {\n Issue: { displayName: \"Issue\", icon: \"bi-lightbulb-fill\" },\n PullRequest: { displayName: \"Pull request\", icon: \"bi-git\" },\n};\n\nconst GithubKanbanTicket = ({\n metadata: { features },\n payload: {\n _links,\n labels,\n number,\n state: ticketState,\n title,\n type,\n user,\n url,\n },\n}) => {\n const header = (\n <div className=\"card-header\">\n <div class=\"d-flex justify-content-start gap-3\">\n <i\n className={`bi ${ticketStates[ticketState].icon}`}\n title={ticketStates[ticketState].displayName}\n />\n\n {features.author ? (\n <a\n className=\"d-flex gap-2 link-dark text-truncate\"\n href={user.html_url}\n rel=\"noreferrer\"\n target=\"_blank\"\n >\n <img\n alt={`${user.login}'s GitHub avatar`}\n className=\"img-fluid rounded\"\n src={user.avatar_url}\n style={{ width: 24, height: 24 }}\n />\n\n <span className=\"text-muted\">@{user.login}</span>\n </a>\n ) : null}\n\n <a\n className=\"card-link ms-auto\"\n href={_links?.html?.href ?? url}\n rel=\"noreferrer\"\n role=\"button\"\n target=\"_blank\"\n title=\"Open in new tab\"\n >\n <i className=\"bi bi-share\" />\n </a>\n </div>\n </div>\n );\n\n const titleArea = (\n <span className=\"card-text gap-2\">\n {features.type ? <i className={`bi ${ticketTypes[type].icon}`} /> : null}\n\n <span>\n {[\n `${features.type ? ticketTypes[type].displayName : \"\"} ${\n features.id ? `#${number.toString()}` : \"\"\n }`.trim(),\n\n title,\n ]\n .filter(\n (maybeString) =>\n typeof maybeString === \"string\" && maybeString.length > 0\n )\n .join(\": \")}\n </span>\n </span>\n );\n\n const labelList = features.labels ? (\n <div className=\"d-flex flex-wrap gap-2 m-0\">\n {(labels ?? []).map((label) => (\n <a href={label.url} key={label.id} title={label.description}>\n <span\n className=\"badge text-wrap\"\n style={{ backgroundColor: `#${label.color}` }}\n >\n {label.name}\n </span>\n </a>\n ))}\n </div>\n ) : null;\n\n return (\n <AttractableDiv className=\"card\">\n {header}\n <div\n className=\"card-body d-flex flex-column gap-3\"\n style={{ fontSize: 15 }}\n >\n {titleArea}\n {labelList}\n </div>\n </AttractableDiv>\n );\n};\n\nreturn GithubKanbanTicket(props);\n" }, "devhub.entity.post.Post": { "": "// Ideally, this would be a page\n\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\nconst { getDepositAmountForWriteAccess } = VM.require(\n \"devhub.megha19.near/widget/core.lib.common\"\n);\n\ngetDepositAmountForWriteAccess || (getDepositAmountForWriteAccess = () => {});\nconst { draftState, onDraftStateChange } = VM.require(\n \"devhub.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"}\n props={{\n text: 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.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"}\n props={{\n text: 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.megha19.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.notification.Left": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nif (!props.type) {\n return \"Loading ...\";\n}\n\nconst type = props.type.split(\"/\")[1];\nreturn props.type ? (\n <>\n {type == \"like\"\n ? \"liked your\"\n : type == \"reply\"\n ? \"replied to your\"\n : type == \"edit\"\n ? \"edited your\"\n : type == \"mention\"\n ? \"mentioned you in their\"\n : type}\n <a\n className=\"fw-bold text-muted\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"proposal\",\n id: props.proposal,\n },\n })}\n >\n DevHub proposal\n </a>\n </>\n) : (\n \"Loading ...\"\n);\n" }, "devhub.components.molecule.DropDown": { "": "const options = props.options; // [{label:\"\",value:\"\"}]\nconst label = props.label;\nconst onUpdate = props.onUpdate ?? (() => {});\nconst selectedValue = props.selectedValue;\nconst [selected, setSelected] = useState(selectedValue);\n\nconst StyledDropdown = styled.div`\n .drop-btn {\n width: 100%;\n max-width: 200px;\n text-align: left;\n padding-inline: 10px;\n }\n\n .dropdown-item.active,\n .dropdown-item:active {\n background-color: #f0f0f0 !important;\n color: black;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n`;\n\nuseEffect(() => {\n onUpdate(selected);\n}, [selected]);\n\nreturn (\n <div>\n <div class=\"dropdown w-100\">\n <StyledDropdown>\n <button\n class=\"btn drop-btn text-truncate dropdown-toggle bg-white border rounded-2\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n >\n {label} {selected && label && \": \"} {selected.label}\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end dropdown-menu-lg-start px-2 shadow\">\n {options.map((item) => (\n <li\n style={{ borderRadius: \"5px\" }}\n class=\"dropdown-item cursor-pointer link-underline link-underline-opacity-0\"\n onClick={() => {\n if (selected.label !== item.label) {\n setSelected(item);\n }\n }}\n >\n {item.label}\n </li>\n ))}\n </ul>\n </StyledDropdown>\n </div>\n </div>\n);\n" }, "devhub.entity.addon.blog.Configurator": { "": "const { data, handle, onSubmit } = props;\n\nconst { Tile } =\n VM.require(\"devhub.megha19.near/widget/devhub.components.molecule.Tile\") ||\n (() => <></>);\n\nif (!Tile) {\n return <div>Loading...</div>;\n}\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n padding: 20px;\n`;\n\nconst Item = styled.div`\n padding: 10px;\n margin: 5px;\n display: flex;\n align-items: center;\n flex-direction: row;\n gap: 10px;\n`;\n\nconst EditableField = styled.input`\n flex: 1;\n`;\n\nconst initialData = data;\n\nreturn (\n <Tile className=\"p-3 bg-white\">\n <Container>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.addon.blog.editor.index\"}\n props={{\n data,\n handle,\n }}\n />\n </Container>\n </Tile>\n);\n" }, "core.lib.data-request": { "": "const DataRequest = {\n /**\n * Requests all the data from non-empty pages of the paginated API.\n *\n * **Notice: currently expected to work only with array responses.**\n *\n * @param {object} parameters\n * \tRequest parameters including the number of page to start with,\n * \tand an accumulated response buffer, if it exists.\n *\n * @param {array | null | undefined} parameters.buffer\n * @param {number} parameters.startWith\n *\n * @param {(pageNumber: number) => array} requestByNumber\n *\n * @returns {array} The final accumulated response.\n */\n paginated: (requestByNumber, { buffer, startWith }) => {\n const startPageNumber = startWith ?? 1,\n accumulatedResponse = buffer ?? [];\n\n const latestResponse = requestByNumber(startPageNumber) ?? [];\n\n if (latestResponse.length === 0) {\n return accumulatedResponse;\n } else {\n return DataRequest.paginated(requestByNumber, {\n buffer: [...accumulatedResponse, ...latestResponse],\n startWith: startPageNumber + 1,\n });\n }\n },\n};\n\nreturn { DataRequest };\n" }, "devhub.feature.post-search.by-author": { "": "const { getAllAuthors } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nif (!getAllAuthors) {\n return <p>Loading modules...</p>;\n}\n\nconst selectedAuthors = props.author ? [{ name: props.author }] : [];\n\nconst authors = getAllAuthors();\n\nif (authors === null) {\n return <div>Loading ...</div>;\n}\n\nconst onChange = (selectedAuthors) =>\n props.onAuthorSearch(selectedAuthors[0]?.name);\n\nreturn (\n <Typeahead\n clearButton\n id=\"basic-typeahead-single\"\n labelKey=\"name\"\n options={authors.map((author) => ({ name: author }))}\n placeholder=\"Search by author\"\n selected={selectedAuthors}\n {...{ onChange }}\n />\n);\n" }, "devhub.page.community.configuration": { "": "const { Tile } =\n VM.require(\"devhub.megha19.near/widget/devhub.components.molecule.Tile\") ||\n (() => <></>);\n\nif (!Tile) {\n return <div>Loading...</div>;\n}\n\nconst {\n permissions,\n handle,\n community,\n setCommunityAddons,\n deleteCommunity,\n updateCommunity,\n} = props;\n\nconst [communityData, setCommunityData] = useState(community || {});\nconst [selectedAddon, setSelectedAddon] = useState(null);\nconst [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);\n\nconst sectionSubmit = (sectionData) => {\n const updatedCommunityData = {\n ...Object.entries(sectionData).reduce(\n (update, [propertyKey, propertyValue]) => ({\n ...update,\n\n [propertyKey]:\n typeof propertyValue !== \"string\" || (propertyValue?.length ?? 0) > 0\n ? propertyValue ?? null\n : null,\n }),\n\n communityData\n ),\n };\n setCommunityData(updatedCommunityData);\n setHasUnsavedChanges(true);\n};\n\nconst hasConfigurePermissions = permissions.can_configure;\nconst hasDeletePermissions = permissions.can_delete;\n\nreturn (\n <div\n className=\"d-flex flex-column align-items-center gap-4 w-100 p-4\"\n style={{ maxWidth: 960, marginBottom: \"60px\" }}\n >\n <Tile className={\"bg-white\"}>\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.BrandingConfigurator\"\n }\n props={{\n onSubmit: sectionSubmit,\n data: communityData,\n hasConfigurePermissions,\n link: `/devhub.megha19.near/widget/app?page=community&handle=${handle}`,\n }}\n />\n </Tile>\n <Tile className={\"p-3 bg-white\"}>\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.ConfigurationSection\"\n }\n props={{\n title: \"Community Information\",\n hasConfigurePermissions,\n Configurator: (p) => (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.InformationConfigurator\"\n }\n props={{\n data: communityData,\n onSubmit: sectionSubmit,\n ...p,\n }}\n />\n ),\n }}\n />\n </Tile>\n <Tile className={\"p-3 bg-white\"}>\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.ConfigurationSection\"\n }\n props={{\n title: \"About\",\n hasConfigurePermissions,\n Configurator: (p) => (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.AboutConfigurator\"\n }\n props={{\n data: communityData,\n onSubmit: sectionSubmit,\n ...p,\n }}\n />\n ),\n }}\n />\n </Tile>\n <Tile className={\"p-3 bg-white\"}>\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.ConfigurationSection\"\n }\n props={{\n title: \"Community Admins\",\n hasConfigurePermissions,\n Configurator: (p) => (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.AccessControlConfigurator\"\n }\n props={{\n data: communityData,\n onSubmit: sectionSubmit,\n ...p,\n }}\n />\n ),\n }}\n />\n </Tile>\n {hasConfigurePermissions && (\n <Tile className={\"p-3 bg-white\"}>\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.ConfigurationSection\"\n }\n props={{\n title: \"Add-Ons\",\n hasConfigurePermissions,\n Configurator: (p) => (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.AddonsConfigurator\"\n }\n props={{\n data: communityData.addons || [],\n onSubmit: (v) => setCommunityAddons({ handle, addons: v }),\n ...p,\n }}\n />\n ),\n }}\n />\n </Tile>\n )}\n {hasDeletePermissions && (\n <div\n className=\"d-flex justify-content-center gap-4 p-4 w-100\"\n style={{ maxWidth: 896 }}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-lg btn-outline-danger border-none\" },\n label: \"Delete community\",\n onClick: () => deleteCommunity({ handle }),\n }}\n />\n </div>\n )}\n {hasConfigurePermissions && hasUnsavedChanges && (\n <div\n className=\"position-fixed end-0 bottom-0 bg-transparent pe-4 pb-4\"\n style={{ borderTopLeftRadius: \"100%\" }}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-lg btn-success\" },\n icon: { type: \"svg_icon\", variant: \"floppy_drive\" },\n label: \"Save\",\n onClick: () =>\n updateCommunity({ handle, community: communityData }), // TODO : Track changes in State\n }}\n />\n </div>\n )}\n </div>\n);\n" }, "devhub.page.addon": { "": "const Container = styled.div`\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n position: relative;\n padding: 0 1rem;\n\n @media screen and (max-width: 960px) {\n padding: 0 1rem;\n }\n`;\n\nconst Content = styled.div`\n flex: 1;\n padding: 20px;\n overflow: auto;\n`;\n\nconst SettingsButton = styled.button`\n position: absolute;\n top: 10px;\n right: 10px;\n\n background-color: #fff;\n display: flex;\n padding: 14px 16px;\n align-items: center;\n gap: 16px;\n width: 50px;\n height: 50px;\n\n border-radius: 4px;\n border: 1px solid #00ec97;\n\n z-index: 10;\n\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n\n &:hover {\n transform: translateY(2px);\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\n }\n\n &:active {\n transform: translateY(0);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\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\nconst { addon, permissions, handle } = props;\n\nconst { getAllAddons, setCommunityAddon } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nif (!getAllAddons || !setCommunityAddon) {\n return <p>Loading modules...</p>;\n}\n\nconst availableAddons = getAllAddons();\n\nconst addonMatch = (availableAddons ?? []).find(\n (it) => it.id === addon.addon_id\n);\n\nif (!addonMatch) {\n return (\n <CenteredMessage height={\"384px\"}>\n <h2>Addon with id: \"{addon.addon_id}\" not found.</h2>\n </CenteredMessage>\n );\n}\n\nconst config = JSON.parse(addon.parameters || \"null\");\n\nconst ButtonRow = styled.div`\n display: flex;\n justify-content: space-between;\n`;\n\nconst [view, setView] = useState(props.view || \"viewer\");\n\nif (\"devhub.megha19.near\" !== \"devhub.near\") {\n addonMatch.configurator_widget = addonMatch.configurator_widget.replace(\n \"devhub.near/\",\n \"devhub.megha19.near/\"\n );\n addonMatch.view_widget = addonMatch.view_widget.replace(\n \"devhub.near/\",\n \"devhub.megha19.near/\"\n );\n}\n\nreturn (\n <Container>\n {permissions.can_configure && addonMatch.configurator_widget !== \"\" && (\n <SettingsButton\n onClick={() => setView(view === \"configure\" ? \"view\" : \"configure\")}\n >\n {view === \"configure\" ? (\n <span className=\"bi bi-x\"></span>\n ) : (\n <span className=\"bi bi-gear\"></span>\n )}\n </SettingsButton>\n )}\n <Content>\n {view === \"configure\" ? (\n <Widget\n src={addonMatch.configurator_widget}\n props={{\n ...config,\n data: config,\n onSubmit: (data) => {\n setCommunityAddon({\n handle,\n addon: {\n ...addon,\n parameters: JSON.stringify(data),\n },\n });\n },\n handle, // this is temporary prop drilling until kanban and github are migrated\n permissions,\n }}\n />\n ) : (\n <Widget\n src={addonMatch.view_widget}\n props={{\n ...config,\n data: config,\n handle,\n permissions,\n transactionHashes: props.transactionHashes,\n }}\n />\n )}\n </Content>\n </Container>\n);\n" }, "devhub.entity.proposal.VerificationStatus": { "": "const receiverAccount = props.receiverAccount;\nconst showGetVerifiedBtn = props.showGetVerifiedBtn;\nconst [verificationStatus, setVerificationStatus] = useState(null);\nconst imageSize = props.imageSize ?? 40;\n\nconst WarningImg =\n \"https://ipfs.near.social/ipfs/bafkreieq4222tf3hkbccfnbw5kpgedm3bf2zcfgzbnmismxav2phqdwd7q\";\n\nconst SuccessImg =\n \"https://ipfs.near.social/ipfs/bafkreidqveupkcc7e3rko2e67lztsqrfnjzw3ceoajyglqeomvv7xznusm\";\n\nuseEffect(() => {\n if (\n receiverAccount.length === 64 ||\n (receiverAccount ?? \"\").includes(\".near\")\n ) {\n useCache(\n () =>\n asyncFetch(\n `https://neardevhub-kyc-proxy.shuttleapp.rs/kyc/${receiverAccount}`\n ).then((res) => {\n let displayableText = \"\";\n switch (res.body.kyc_status) {\n case \"Approved\":\n displayableText = \"Verified\";\n break;\n case \"Pending\":\n displayableText = \"Pending\";\n break;\n default:\n displayableText = \"Not Verfied\";\n break;\n }\n setVerificationStatus(displayableText);\n }),\n \"ky-check-proposal\" + receiverAccount,\n { subscribe: false }\n );\n }\n}, [receiverAccount]);\n\nconst DropdowntBtnContainer = styled.div`\n font-size: 13px;\n min-width: 150px;\n\n .custom-select {\n position: relative;\n }\n\n .select-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n border: 1px solid #ccc;\n border-radius-top: 5px;\n cursor: pointer;\n background-color: #fff;\n border-radius: 5px;\n }\n\n .no-border {\n border: none !important;\n }\n\n .options-card {\n position: absolute;\n top: 100%;\n left: 0;\n width: 200%;\n border: 1px solid #ccc;\n background-color: #fff;\n padding: 0.5rem;\n z-index: 99;\n font-size: 13px;\n border-radius: 0.375rem !important;\n }\n\n .left {\n right: 0 !important;\n left: auto !important;\n }\n\n @media screen and (max-width: 768px) {\n .options-card {\n right: 0 !important;\n left: auto !important;\n }\n }\n\n .option {\n margin-block: 2px;\n padding: 5px;\n cursor: pointer;\n border-bottom: 1px solid #f0f0f0;\n transition: background-color 0.3s ease;\n border-radius: 0.375rem !important;\n }\n\n .option:hover {\n background-color: #f0f0f0; /* Custom hover effect color */\n }\n\n .option:last-child {\n border-bottom: none;\n }\n\n .selected {\n background-color: #f0f0f0;\n }\n\n .disabled {\n background-color: #f4f4f4 !important;\n cursor: not-allowed !important;\n font-weight: 500;\n color: #b3b3b3;\n }\n\n .disabled .circle {\n opacity: 0.5;\n }\n\n .circle {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n }\n\n .grey {\n background-color: #818181;\n }\n\n .green {\n background-color: #04a46e;\n }\n\n a:hover {\n text-decoration: none;\n }\n\n .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n`;\n\nconst [kycOptionsOpen, setKycOptions] = useState(false);\n\nconst VerificationBtn = () => {\n const btnOptions = [\n {\n src: \"https://ipfs.near.social/ipfs/bafkreidqveupkcc7e3rko2e67lztsqrfnjzw3ceoajyglqeomvv7xznusm\",\n label: \"KYC\",\n description: \"Choose this if you are an individual.\",\n value: \"KYC\",\n },\n {\n src: \"https://ipfs.near.social/ipfs/bafkreic5ksax6b45pelvxm6a2v2j465jgbitpzrxtzpmn6zehl23gocwxm\",\n label: \"KYB\",\n description: \"Choose this if you are a business or corporate entity..\",\n value: \"KYB\",\n },\n ];\n\n const toggleDropdown = () => {\n setKycOptions(!kycOptionsOpen);\n };\n\n return (\n <DropdowntBtnContainer>\n <div\n className=\"custom-select\"\n tabIndex=\"0\"\n id=\"getVerifiedButton\"\n onClick={toggleDropdown}\n onBlur={() => {\n setTimeout(() => {\n setKycOptions(false);\n }, 100);\n }}\n >\n <div\n className={\n \"select-header no-border black-btn btn d-inline-flex align-items-center gap-2\"\n }\n >\n <div className=\"d-flex align-items-center gap-1\">\n Get Verified\n <i class=\"bi bi-box-arrow-up-right\"></i>\n </div>\n </div>\n\n {kycOptionsOpen && (\n <div className=\"options-card left\">\n {btnOptions.map((option) => (\n <a\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n href={\n option.value === \"KYC\"\n ? \"https://go.fractal.id/near-social-kyc\"\n : \"https://go.fractal.id/near-social-kyb\"\n }\n >\n <div\n key={option.value}\n className={`option ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n >\n <div className={`d-flex gap-2 align-items-center`}>\n <img src={option.src} height={30} />\n <div>\n <div className=\"fw-bold\">{option.label}</div>\n <div className=\"text-muted text-xs\">\n {option.description}\n </div>\n </div>\n </div>\n </div>\n </a>\n ))}\n </div>\n )}\n </div>\n </DropdowntBtnContainer>\n );\n};\n\nreturn (\n <div>\n <div className=\"d-flex text-black justify-content-between align-items-center\">\n <div className=\"d-flex\" style={{ gap: \"12px\" }}>\n <img\n className=\"align-self-center object-fit-cover\"\n src={verificationStatus === \"Verified\" ? SuccessImg : WarningImg}\n height={imageSize}\n />\n <div className=\"d-flex flex-column justify-content-center\">\n <div className=\"h6 mb-0\">Fractal</div>\n <div className=\"text-sm text-muted\">{verificationStatus}</div>\n </div>\n </div>\n {verificationStatus !== \"Verified\" && showGetVerifiedBtn && (\n <VerificationBtn />\n )}\n </div>\n </div>\n);\n" }, "devhub.components.island.connect": { "": "const { getFeaturedCommunities } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nif (!getFeaturedCommunities) {\n return <p>Loading modules...</p>;\n}\n\nconst communities = getFeaturedCommunities();\n\nif (!communities) {\n return <p>Loading communities...</p>;\n}\n\nconst [startIndex, setStartIndex] = useState(0);\nconst [endIndex, setEndIndex] = useState(2);\n\nconst DescriptionHeader = styled.h2`\n color: #f4f4f4;\n font-size: 1.75rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 43.2px */\n\n @media screen and (max-width: 786px) {\n font-size: 1.5rem;\n }\n`;\n\nconst Description = styled.p`\n color: #f4f4f4;\n text-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n\n @media screen and (max-width: 786px) {\n font-size: 1rem;\n }\n`;\n\nconst imageSource =\n \"https://ipfs.near.social/ipfs/bafkreic7wxhocbnxoo63uh6n2ur3otykbzouymobt3ebgd2b4dmdiu3764\";\n\nconst CardBody = styled.div`\n border-radius: 1rem;\n border: 1px solid #00ec97;\n background: #3f4040;\n\n display: flex;\n max-width: 31.5%;\n height: 12rem;\n padding: 1.5rem;\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n gap: 0.5rem;\n flex-shrink: 0;\n align-self: stretch;\n\n h3 {\n color: #00ec97;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 40px */\n }\n\n p {\n color: #818181;\n font-size: 1.125rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n }\n\n a {\n color: #00ec97;\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n\n &:hover {\n text-decoration: none;\n }\n }\n\n @media screen and (max-width: 768px) {\n max-width: 80%;\n }\n`;\n\nconst Card = ({ title, description, href }) => {\n return (\n <CardBody>\n <h3>{title}</h3>\n <p>{description}</p>\n <a href={href}>Learn more →</a>\n </CardBody>\n );\n};\n\nconst Cards = communities.map((com) => {\n return {\n title: com.name,\n description: com.description,\n href: \"/devhub.megha19.near/widget/app?page=community&handle=\" + com.handle,\n };\n});\n\nconst ForwardButton = styled.button`\n all: unset;\n position: absolute;\n right: 0;\n\n margin: 1rem;\n\n &:hover,\n &:active {\n border: none;\n outline: none;\n }\n\n ${endIndex >= Cards.length - 1 && \"svg {transform: rotate(180deg);}\"}\n`;\n\nconst handleForward = () => {\n if (endIndex <= Cards.length - 1) {\n setStartIndex(endIndex + 1);\n setEndIndex(endIndex + 3);\n } else {\n setStartIndex(0);\n setEndIndex(2);\n }\n};\n\nconst CTA = styled.a`\n color: #00ec97 !important;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n\n @media screen and (max-width: 768px) {\n font-size: 1.25rem;\n }\n`;\n\nconst Subheading = styled.h3`\n color: #8a8e93;\n font-size: 2.25rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 43.2px */\n\n padding: 3rem;\n padding-top: 0;\n\n @media screen and (max-width: 786px) {\n padding: 1rem;\n padding-top: 0;\n font-size: 1.5rem;\n }\n`;\n\nconst Container = styled.div`\n width: 100%;\n display: flex;\n position: relative;\n align-items: center;\n\n @media screen and (max-width: 768px) {\n flex-direction: column;\n }\n`;\n\nconst DescriptionContainer = styled.div`\n padding: 3rem;\n width: 55%;\n\n @media screen and (max-width: 768px) {\n padding: 1rem;\n width: 100%;\n }\n`;\n\nconst ImageContainer = styled.div`\n position: absolute;\n top: 25%;\n right: 0;\n\n width: 50%;\n height: 65%;\n\n @media screen and (max-width: 768px) {\n position: relative;\n padding: 0 1rem;\n\n height: 225px;\n width: 100%;\n }\n`;\n\nconst Image = styled.img`\n width: 100%;\n height: 90%;\n object-fit: cover;\n clip-path: polygon(15% 0, 100% 0%, 100% 100%, 0% 100%);\n\n @media screen and (max-width: 768px) {\n clip-path: none;\n }\n`;\n\nconst CardsContainer = styled.div`\n padding: 3rem;\n padding-top: 0;\n\n position: relative;\n display: flex;\n flex-direction: row;\n gap: 1rem;\n width: 100%;\n align-items: center;\n\n @media screen and (max-width: 768px) {\n display: none;\n }\n`;\n\nconst ArrowIcon = () => {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"48\"\n height=\"49\"\n viewBox=\"0 0 48 49\"\n fill=\"none\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M32.9999 24.5L17.9999 39.5L15.8999 37.4L28.7999 24.5L15.8999 11.6L17.9999 9.5L32.9999 24.5Z\"\n fill=\"#00EC97\"\n />\n </svg>\n );\n};\n\nconst CTAContainer = styled.div`\n padding: 3rem;\n padding-top: 0;\n\n @media screen and (max-width: 786px) {\n padding: 1rem;\n padding-top: 0;\n }\n`;\n\nconst MobileCards = styled.div`\n display: none;\n\n @media screen and (max-width: 768px) {\n display: flex;\n padding: 1rem;\n flex-direction: row;\n overflow-x: auto;\n gap: 1rem;\n }\n`;\n\nconst Content = (\n <>\n <Container>\n <DescriptionContainer>\n <DescriptionHeader>\n Communities are the lifeblood of /dev/hub\n </DescriptionHeader>\n <Description>\n We believe that communities are the foundation of a decentralized\n ecosystem. Explore and engage with our diverse range of communities\n today.\n </Description>\n </DescriptionContainer>\n <ImageContainer>\n <Image src={imageSource} />\n </ImageContainer>\n </Container>\n <Subheading>Featured Communities</Subheading>\n <CardsContainer>\n {Cards.slice(startIndex, endIndex + 1).map((card, idx) => (\n <Card\n title={card.title}\n description={card.description}\n href={card.href}\n key={`project-card-${idx}`}\n />\n ))}\n <ForwardButton onClick={handleForward}>\n <ArrowIcon />\n </ForwardButton>\n </CardsContainer>\n <MobileCards>\n {Cards.map((card, idx) => (\n <Card\n title={card.title}\n description={card.description}\n href={card.href}\n key={`mobile-card-${idx}`}\n />\n ))}\n </MobileCards>\n <CTAContainer>\n <CTA href=\"/devhub.megha19.near/widget/app?page=communities\">\n Explore all communities →\n </CTA>\n </CTAContainer>\n </>\n);\n\nreturn (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.island.home-section\"\n props={{\n title: \"/connect\",\n children: Content,\n background: true,\n }}\n />\n);\n" }, "core.lib.uuid": { "": "const uuid = () =>\n [Date.now().toString(16)]\n .concat(\n Array.from(\n { length: 4 },\n () => Math.floor(Math.random() * 0xffffffff) & 0xffffffff\n ).map((value) => value.toString(16))\n )\n .join(\"-\");\n\nconst withUUIDIndex = (data) => {\n const id = uuid();\n\n return Object.fromEntries([[id, { ...data, id }]]);\n};\n\nreturn { uuid, withUUIDIndex };\n" }, "devhub.components.island.home-section": { "": "const title = props.title;\nconst titleColor = props.titleColor;\nconst description = props.description;\nconst children = props.children;\nconst background = props.background;\n\nconst Section = styled.div`\n ${background && \"background: #292929;\"}\n ${background && \"color: #F4F4F4;\"}\n width: 100%;\n`;\n\nconst SectionHeader = styled.h2`\n color: ${titleColor || \"#00ec97\"};\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.24px;\n\n @media screen and (max-width: 768px) {\n font-size: 1.25rem;\n }\n`;\n\nconst SectionDescription = styled.h3`\n color: #151515;\n font-size: 2.25rem;\n font-style: normal;\n font-weight: 700;\n line-height: 110%; /* 39.6px */\n letter-spacing: -0.72px;\n\n margin-bottom: 2.25rem;\n margin-top: 2.25rem;\n\n max-width: 40rem;\n\n @media screen and (max-width: 768px) {\n margin-bottom: 1.5rem;\n margin-top: 1.5rem;\n font-size: 1.5rem;\n }\n`;\n\nconst Container = styled.div`\n padding: 3rem;\n padding-bottom: 0;\n\n @media screen and (max-width: 768px) {\n padding: 1rem;\n }\n`;\n\nreturn (\n <Section>\n <Container>\n <SectionHeader>{title}</SectionHeader>\n {description && <SectionDescription>{description}</SectionDescription>}\n </Container>\n {children}\n </Section>\n);\n" }, "devhub.page.community.index": { "": "const { normalize } = VM.require(\"devhub.megha19.near/widget/core.lib.stringUtils\");\n\nnormalize || (normalize = () => {});\n\nconst Button = styled.button`\n height: 40px;\n font-size: 14px;\n border-color: #e3e3e0;\n background-color: #ffffff;\n`;\n\nconst Banner = styled.div`\n max-width: 100%;\n min-height: 240px;\n height: 240px;\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\nconst NavUnderline = styled.ul`\n cursor: pointer;\n a {\n color: #151515;\n text-decoration: none;\n }\n\n a.active {\n font-weight: bold;\n border-bottom: 4px solid #00ec97;\n }\n border-bottom: 1px solid #cccccc;\n`;\n\nconst { tab, permissions, community, view } = props;\n\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nif (!href) {\n return <></>;\n}\n\nif (!tab) {\n tab = \"Announcements\";\n}\n\ntab = normalize(tab);\n\nconst [isLinkCopied, setLinkCopied] = useState(false);\n\nconst tabs = [];\n\n(community.addons || []).map((addon) => {\n addon.enabled &&\n tabs.push({\n title: addon.display_name,\n view: \"devhub.megha19.near/widget/devhub.page.addon\",\n params: {\n addon,\n handle: community.handle,\n transactionHashes: props.transactionHashes,\n },\n });\n});\n\nconst onShareClick = () =>\n clipboard\n .writeText(\n href({\n gateway: \"near.social\",\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"community\", handle: community.handle },\n })\n )\n .then(setLinkCopied(true));\n\nlet currentTab = tabs.find((it) => normalize(it.title) === tab);\n\nconst CommunityName = styled.span`\n color: #151515;\n font-size: 2.25rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 48px */\n\n @media screen and (max-width: 768px) {\n font-size: 1.5rem;\n }\n`;\n\nconst CommunityDetails = styled.span`\n color: #818181;\n font-size: 1rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n`;\n\nfunction trimHttps(url) {\n if (url.startsWith(\"https://\")) {\n return url.substring(8);\n }\n return url;\n}\n\n// some communities have url as handle (eg: devhub platform) while others has correct handle\nfunction checkTelegramHandle(tg) {\n const pattern = /https:\\/\\/t.me\\/(.*)/;\n const includesHttp = tg.match(pattern);\n const handle = includesHttp ? includesHttp[1] : tg;\n return { handle, url: \"https://t.me/\" + handle };\n}\n\nconst socialLinks = [\n ...((community.website_url?.length ?? 0) > 0\n ? [\n {\n href: `https://${trimHttps(community.website_url)}`,\n iconClass: \"bi bi-globe\",\n name: trimHttps(community.website_url),\n },\n ]\n : []),\n\n ...((community.github_handle?.length ?? 0) > 0\n ? [\n {\n href: `https://github.com/${community.github_handle}`,\n iconClass: \"bi bi-github\",\n name: community.github_handle,\n },\n ]\n : []),\n\n ...((community.twitter_handle?.length ?? 0) > 0\n ? [\n {\n href: `https://twitter.com/${community.twitter_handle}`,\n iconClass: \"bi bi-twitter\",\n name: community.twitter_handle,\n },\n ]\n : []),\n\n ...(community.telegram_handle?.length > 0\n ? [\n {\n href: checkTelegramHandle(community.telegram_handle).url,\n iconClass: \"bi bi-telegram\",\n name: checkTelegramHandle(community.telegram_handle).handle,\n },\n ]\n : []),\n];\n\nconst NavlinksContainer = styled.div`\n //background: white;\n padding: 0 3rem;\n\n @media screen and (max-width: 960px) {\n padding: 0 1rem;\n }\n`;\n\nreturn (\n <div\n className=\"d-flex flex-column gap-3 w-100\"\n style={{ background: \"#F4F4F4\" }}\n >\n <Banner\n className=\"object-fit-cover\"\n style={{\n background: `center / cover no-repeat url(${community.banner_url})`,\n }}\n />\n\n <div className=\"container d-flex flex-wrap justify-content-between align-items-center align-items-md-start gap-4\">\n <div className=\"d-flex flex-column ms-3\">\n <div className=\"position-relative\">\n <div style={{ width: 150, height: 45 }}>\n <img\n alt=\"Loading logo...\"\n className=\"rounded-circle position-absolute\"\n width=\"160\"\n height=\"160\"\n src={community.logo_url}\n style={{ top: -124 }}\n />\n </div>\n </div>\n\n <div className=\"d-flex flex-column gap-3 ps-md-3 pt-md-3 pb-md-2\">\n <CommunityName className=\"text-nowrap\">\n {community.name}\n </CommunityName>\n <CommunityDetails>{community.description}</CommunityDetails>\n </div>\n\n <div className=\"mt-3 ps-3 d-flex gap-3 align-items-center\">\n {socialLinks.map((link, index) => (\n <a\n href={link.href}\n style={{\n marginLeft: index !== 0 ? \"0px\" : \"0px\",\n color: \"#818181\",\n }}\n key={link.href}\n target=\"_blank\"\n >\n <i className={link.iconClass}></i>\n </a>\n ))}\n </div>\n </div>\n\n <div className=\"d-flex align-items-end gap-3 ms-auto mb-md-5 me-4\">\n {permissions.can_configure && (\n <Link\n to={`/devhub.megha19.near/widget/app?page=community.configuration&handle=${community.handle}`}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-light text-dark shadow-none\" },\n notRounded: true,\n style: {\n display: \"flex\",\n padding: \"0.75rem 1rem\",\n alignItems: \"center\",\n gap: \"16px\",\n\n borderRadius: \"4px\",\n border: \"1px solid #00EC97\",\n background: \"rgba(129, 129, 129, 0.00)\",\n },\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-gear-wide-connected\",\n },\n label: \"Configure community\",\n }}\n />\n </Link>\n )}\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-light text-dark shadow-none\" },\n notRounded: true,\n style: {\n display: \"flex\",\n padding: \"0.75rem 1rem\",\n alignItems: \"center\",\n gap: \"16px\",\n\n borderRadius: \"4px\",\n border: \"1px solid #00EC97\",\n background: \"rgba(129, 129, 129, 0.00)\",\n },\n label: \"Share ↗\",\n onClick: onShareClick,\n onMouseLeave: () => setLinkCopied(false),\n title: \"Copy link to clipboard\",\n }}\n />\n </div>\n </div>\n <NavlinksContainer>\n <NavUnderline className=\"nav gap-4 my-4\">\n {tabs.map(\n ({ title }) =>\n title && (\n <li className=\"nav-item\" key={title}>\n <Link\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"community\",\n handle: community.handle,\n tab: normalize(title),\n },\n })}\n aria-current={tab === normalize(title) && \"page\"}\n className={[\n \"d-inline-flex gap-2\",\n tab === normalize(title) ? \"nav-link active\" : \"nav-link\",\n ].join(\" \")}\n >\n <span>{title}</span>\n </Link>\n </li>\n )\n )}\n </NavUnderline>\n {currentTab.title === \"Activity\" && (\n <div\n className=\"my-4 d-flex align-items-center justify-content-between\"\n style={{ gap: \"2.5rem\" }}\n >\n <div class=\"d-flex align-items-center justify-content-between\">\n <small class=\"text-muted\">\n <span>Required tags:</span>\n <Link\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"feed\", tag: community.tag },\n })}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.atom.Tag\"}\n props={{\n tag: community.tag,\n }}\n />\n </Link>\n </small>\n </div>\n {context.accountId && (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.components.molecule.PostControls\"\n }\n props={{\n title: \"Post\",\n href: href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"create\",\n labels: [community.tag],\n },\n }),\n }}\n />\n )}\n </div>\n )}\n </NavlinksContainer>\n {currentTab && (\n <div className=\"d-flex w-100 h-100\" key={currentTab.title}>\n <Widget\n src={currentTab.view}\n props={{\n ...currentTab.params,\n view, // default view for an addon, can come as a prop from a community or from a direct link to page.addon\n\n // below is temporary prop drilling until kanban and github are migrated\n permissions,\n handle: community.handle,\n }}\n />\n </div>\n )}\n </div>\n);\n" }, "devhub.entity.addon.kanban.post_board": { "": "const { getPostsByLabel } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\nconst { getPost } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\ngetPost || (getPost = () => {});\ngetPostsByLabel || (getPostsByLabel = () => {});\n\nconst postTagsToIdSet = (tags) => {\n return new Set(\n (tags ?? [])?.map((tag) => getPostsByLabel({ label: tag }) ?? []).flat(1)\n );\n};\n\nconst sortByValues = {\n descendingAmount: \"descending-amount\",\n ascendingAmount: \"ascending-amount\",\n descendingDate: \"descending-date\",\n ascendingDate: \"ascending-date\",\n ascendingAuthor: \"ascending-author\",\n descendingAuthor: \"descending-author\",\n ascendingSponsor: \"ascending-sponsor\",\n descendingSponsor: \"descending-sponsor\",\n descendingLikes: \"descending-likes\",\n ascendingLikes: \"ascending-likes\",\n};\n\nconst configToColumnData = ({ columns, tags }) =>\n Object.entries(columns).reduce((registry, [columnId, column]) => {\n const postIds = (getPostsByLabel({ label: column.tag }) ?? []).reverse();\n return {\n ...registry,\n [columnId]: {\n ...column,\n postIds: postIds,\n },\n };\n }, {});\n\nconst basicAlphabeticalComparison = (a, b) => {\n if (a < b) return -1;\n if (a > b) return 1;\n return 0;\n};\n\nconst KanbanPostBoard = ({ metadata, payload }) => {\n const boardData = Object.entries(configToColumnData(payload) ?? {});\n\n const view = boardData.map(([columnId, column]) => {\n const data = [];\n column.postIds?.map((postId) => {\n if (postId) {\n const postData = getPost({\n post_id: postId ? parseInt(postId) : 0,\n });\n data.push(postData);\n }\n });\n\n // sort data by selected sorting mechanism\n switch (metadata.ticket.sortBy) {\n case sortByValues.descendingAmount:\n data.sort((a, b) => b?.snapshot?.amount - a?.snapshot?.amount);\n break;\n case sortByValues.ascendingAmount:\n data.sort((a, b) => a?.snapshot?.amount - b?.snapshot?.amount);\n break;\n case sortByValues.descendingDate:\n data.sort(\n (a, b) =>\n parseInt(b?.snapshot?.timestamp) - parseInt(a?.snapshot?.timestamp)\n );\n break;\n case sortByValues.ascendingDate:\n data.sort(\n (a, b) =>\n parseInt(a?.snapshot?.timestamp) - parseInt(b?.snapshot?.timestamp)\n );\n break;\n case sortByValues.ascendingAuthor:\n data.sort((a, b) =>\n basicAlphabeticalComparison(a.author_id, b.author_id)\n );\n break;\n case sortByValues.descendingAuthor:\n data.sort((a, b) =>\n basicAlphabeticalComparison(b.author_id, a.author_id)\n );\n break;\n case sortByValues.ascendingSponsor:\n data.sort((a, b) =>\n basicAlphabeticalComparison(\n a?.snapshot?.requested_sponsor || a?.snapshot?.supervisor,\n b?.snapshot?.requested_sponsor || b?.snapshot?.supervisor\n )\n );\n break;\n case sortByValues.descendingSponsor:\n data.sort((a, b) =>\n basicAlphabeticalComparison(\n b?.snapshot?.requested_sponsor || b?.snapshot?.supervisor,\n a?.snapshot?.requested_sponsor || a?.snapshot?.supervisor\n )\n );\n break;\n case sortByValues.descendingLikes:\n data.sort((a, b) => b.likes.length - a.likes.length);\n break;\n case sortByValues.ascendingLikes:\n data.sort((a, b) => a.likes.length - b.likes.length);\n break;\n default:\n data;\n break;\n }\n\n return (\n <div\n className=\"col-3\"\n style={{ minWidth: \"300px\" }}\n key={`column-${columnId}-view`}\n >\n <div className=\"card rounded-4\">\n <div\n className={[\n \"card-body d-flex flex-column gap-3 p-2\",\n \"border border-1 rounded-4\",\n ].join(\" \")}\n style={{ height: \"75vh\" }}\n >\n <span className=\"d-flex flex-column py-1\">\n <h6 className=\"card-title h6 d-flex align-items-center gap-2 m-0\">\n {column.title}\n\n <span className=\"badge rounded-pill bg-secondary\">\n {column.postIds.length}\n </span>\n </h6>\n\n <p class=\"text-secondary m-0\">{column.description}</p>\n </span>\n\n <div\n class=\"d-flex flex-column gap-2\"\n style={{ overflow: \"scroll\" }}\n >\n {data.length === column.postIds.length &&\n data.map((postData) => (\n <Widget\n src={`devhub.megha19.near/widget/devhub.entity.addon.${metadata.ticket.type}`}\n props={{\n metadata: { id: postData.postId, ...metadata.ticket },\n\n data: postData,\n }}\n key={postData.postId}\n />\n ))}\n </div>\n </div>\n </div>\n </div>\n );\n });\n\n return (\n <div>\n <div className=\"d-flex flex-column align-items-center gap-2 pb-4 w-100\">\n <h5 className=\"h4 d-inline-flex gap-2 m-0\">\n <span>{metadata?.title}</span>\n </h5>\n\n <p className=\"h6 m-0 py-1 text-secondary text-center\">\n {metadata?.description}\n </p>\n </div>\n <div className=\"d-flex gap-3 w-100\" style={{ overflow: \"scroll\" }}>\n <div\n className={[\n \"d-flex align-items-center justify-content-center w-100 text-black-50 opacity-50\",\n columns.length === 0 ? \"\" : \"d-none\",\n ].join(\" \")}\n style={{ height: 384 }}\n >\n No columns were created so far.\n </div>\n <span className={\"d-flex gap-3 w-100\"}>{view}</span>\n </div>\n </div>\n );\n};\n\nreturn KanbanPostBoard(props);\n" }, "devhub.entity.proposal.LinkedProposalsDropdown": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\nhref || (href = () => {});\n\nconst linkedProposals = props.linkedProposals;\nconst onChange = props.onChange;\nconst [selectedProposals, setSelectedProposals] = useState(linkedProposals);\nconst [proposalsOptions, setProposalsOptions] = useState([]);\nconst [searchProposalId, setSearchProposalId] = useState(\"\");\nconst QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql`;\nconst queryName =\n \"thomasguntenaar_near_devhub_proposals_quebec_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\nuseEffect(() => {\n if (JSON.stringify(linkedProposals) !== JSON.stringify(selectedProposals)) {\n setSelectedProposals(linkedProposals);\n }\n}, [linkedProposals]);\n\nuseEffect(() => {\n if (JSON.stringify(linkedProposals) !== JSON.stringify(selectedProposals)) {\n onChange(selectedProposals);\n }\n}, [selectedProposals]);\n\nfunction 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\nconst buildWhereClause = () => {\n let where = {};\n const { number, text } = separateNumberAndText(searchProposalId);\n\n if (number) {\n where = { proposal_id: { _eq: number }, ...where };\n }\n\n if (text) {\n where = { name: { _ilike: `%${text}%` }, ...where };\n }\n\n return where;\n};\n\nfunction fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `thomasguntenaar_near` },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n}\n\nconst fetchProposals = () => {\n const FETCH_LIMIT = 30;\n const variables = {\n limit: FETCH_LIMIT,\n offset: 0,\n where: buildWhereClause(),\n };\n fetchGraphQL(query, \"GetLatestSnapshot\", variables).then(async (result) => {\n if (result.status === 200) {\n if (result.body.data) {\n const proposalsData =\n result.body.data\n .thomasguntenaar_near_devhub_proposals_quebec_proposals_with_latest_snapshot;\n\n const data = [];\n for (const prop of proposalsData) {\n data.push({\n label: \"# \" + prop.proposal_id + \" : \" + prop.name,\n value: prop.proposal_id,\n });\n }\n setProposalsOptions(data);\n }\n }\n });\n};\n\nuseEffect(() => {\n fetchProposals();\n}, [searchProposalId]);\n\nreturn (\n <>\n {selectedProposals.map((proposal) => {\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <a\n className=\"text-decoration-underline flex-1\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"proposal\",\n id: proposal.value,\n },\n })}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {proposal.label}\n </a>\n <div\n className=\"cursor-pointer\"\n onClick={() => {\n const updatedLinkedProposals = selectedProposals.filter(\n (item) => item.value !== proposal.value\n );\n setSelectedProposals(updatedLinkedProposals);\n }}\n >\n <i class=\"bi bi-trash3-fill\"></i>\n </div>\n </div>\n );\n })}\n\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.DropDownWithSearch\"\n props={{\n selectedValue: \"\",\n onChange: (v) => {\n if (!selectedProposals.some((item) => item.value === v.value)) {\n setSelectedProposals([...selectedProposals, v]);\n }\n },\n options: proposalsOptions,\n showSearch: true,\n searchInputPlaceholder: \"Search by Id\",\n defaultLabel: \"Search proposals\",\n searchByValue: true,\n onSearch: (value) => {\n setSearchProposalId(value);\n },\n }}\n />\n </>\n);\n" }, "devhub.feature.post-search.panel": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nhref || (href = () => {});\n\nState.init({\n tag: props.tag,\n author: props.author,\n term: \"\",\n});\n\nconst updateInput = (term) => {\n State.update({\n term: term,\n });\n};\n\nconst buttonStyle = {\n backgroundColor: \"#0C7283\",\n color: \"#f3f3f3\",\n};\n\nconst PageTitle = styled.h1`\n color: #555555;\n font-size: 24px;\n font-style: normal;\n font-weight: 500;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.24px;\n\n margin: 0;\n margin-bottom: 1rem;\n`;\n\nconst Container = styled.div`\n padding: 24px;\n width: 100%;\n`;\n\nconst PostContainer = styled.div`\n margin: 0 1rem;\n`;\n\nconst InputContainer = styled.div`\n display: flex;\n flex-direction: row;\n position: relative;\n width: 25%;\n\n @media screen and (max-width: 768px) {\n width: 100%;\n }\n`;\n\nconst DropdownContainer = styled.div`\n width: 25%;\n\n @media screen and (max-width: 768px) {\n width: 100%;\n }\n`;\n\nconst StyledDropdown = styled.div`\n button {\n width: 100%;\n text-align: left;\n\n &::after {\n position: absolute;\n right: 8px;\n top: 45%;\n transform: translateX(-50%);\n }\n }\n`;\n\nconst BannerWrapper = styled.div`\n background-color: #ffd2d2;\n .text-sm {\n font-size: 13px;\n }\n`;\n\nreturn (\n <>\n {!props.hideHeader && (\n <Container>\n <div className=\"w-100\">\n <BannerWrapper className=\"d-flex gap-3 align-items-center mb-4 p-3 rounded-3\">\n <div>\n <i class=\"bi bi-exclamation-triangle-fill\"></i>\n </div>\n <div>\n <div className=\"fw-bold\">This page is now archived! </div>\n <div className=\"text-sm\">\n For submitting formal funding proposals from DevDAO, please\n visit the new{\" \"}\n <a\n href=\"https://near.org/devhub.near/widget/app?page=proposals\"\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Proposal Feed\n </a>\n . To brainstorm and share ideas, please visit the relevant{\" \"}\n <a\n href=\"https://near.org/devhub.near/widget/app?page=communities\"\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n communities\n </a>\n .\n </div>\n </div>\n </BannerWrapper>\n <PageTitle>Activity Feed</PageTitle>\n <div>\n <div className=\"d-flex flex-column flex-md-row gap-4\">\n <InputContainer>\n <div className=\"position-absolute d-flex ps-3 flex-column h-100 justify-center\">\n <i class=\"bi bi-search m-auto\"></i>\n </div>\n <input\n type=\"search\"\n className=\"ps-5 form-control border rounded-2\"\n value={state.term ?? \"\"}\n onChange={(e) => updateInput(e.target.value)}\n onKeyDown={(e) => e.key == \"Enter\" && search()}\n placeholder={props.placeholder ?? `Search by content`}\n />\n </InputContainer>\n <DropdownContainer>\n <div class=\"dropdown\">\n <StyledDropdown>\n <button\n class=\"btn dropdown-toggle bg-white border rounded-2\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n >\n Sort: {props.recency === \"all\" ? \"All replies\" : \"Latest\"}{\" \"}\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end dropdown-menu-lg-start px-2 shadow\">\n <li>\n <a\n style={{ borderRadius: \"5px\" }}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"feed\" },\n })}\n >\n Latest\n </a>\n </li>\n <li>\n <a\n style={{ borderRadius: \"5px\" }}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"feed\", recency: \"all\" },\n })}\n >\n All replies\n </a>\n </li>\n </ul>\n </StyledDropdown>\n </div>\n </DropdownContainer>\n <div class=\"dropdown\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.feature.post-search.by-author\"\n props={{\n author: state.author,\n onAuthorSearch: (author) => {\n State.update({ author });\n },\n }}\n />\n </div>\n <div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.feature.post-search.by-tag\"\n props={{\n tag: state.tag,\n onTagSearch: (tag) => {\n State.update({ tag });\n },\n }}\n />\n </div>\n <div className=\"d-flex flex-row-reverse flex-grow-1\">\n {props.children}\n </div>\n </div>\n </div>\n </div>\n </Container>\n )}\n <PostContainer>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.post.List\"\n props={{\n author: state.author,\n tag: state.tag,\n term: state.term,\n recency: props.recency,\n transactionHashes: props.transactionHashes,\n updateTagInput: (tag) => State.update({ tag }),\n }}\n />\n </PostContainer>\n </>\n);\n" }, "devhub.components.molecule.ProfileCard": { "": "const MutedText = styled.span`\n color: #818181;\n\n font-size: 16px;\n font-style: normal;\n font-weight: 400;\n line-height: 20px; /* 125% */\n`;\n\nconst AccountName = styled.span`\n color: #818181;\n font-size: 16px;\n font-style: normal;\n font-weight: 500;\n line-height: 20px;\n\n max-width: 30ch;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n`;\n\nconst ProfileCard = (props) => {\n const accountId = props.accountId ?? context.accountId;\n const link = props.link ?? true;\n // const hideAccountId = props.hideAccountId;\n // const hideName = props.hideName;\n const hideImage = props.hideImage;\n const iconOnly = props.iconOnly;\n const openLinkInNewTab = props.openLinkInNewTab ?? false;\n\n const profile = props.profile ?? Social.getr(`${accountId}/profile`);\n\n const name = profile.name ?? accountId;\n const title = props.title ?? `${name} @${accountId}`;\n const tooltip =\n props.tooltip && (props.tooltip === true ? title : props.tooltip);\n\n let inner = (\n <div className=\"d-flex flex-row justify-content-center align-items-center\">\n {!hideImage && (\n <Widget\n key=\"image\"\n src=\"mob.near/widget/ProfileImage\"\n props={{\n style: { width: \"2.5em\", height: \"2.5em\", marginRight: \"0.3em\" },\n profile,\n accountId,\n className: \"d-inline-block flex-shrink-0\",\n imageClassName: \"rounded-circle w-100 h-100 align-top\",\n }}\n />\n )}\n {!iconOnly && (\n <div className=\"d-flex flex-column gap-1\">\n <AccountName key=\"accountName\">{name}</AccountName>\n <AccountName key=\"accountId\">@{accountId}</AccountName>\n </div>\n )}\n </div>\n );\n\n inner = link ? (\n <a\n href={\n link !== true\n ? link\n : `/mob.near/widget/ProfilePage?accountId=${accountId}`\n }\n target={openLinkInNewTab ? \"_blank\" : \"\"}\n rel=\"noopener noreferrer\"\n className=\"link-dark text-truncate d-inline-flex\"\n >\n {inner}\n </a>\n ) : (\n <span className=\"text-truncate d-inline-flex\">{inner}</span>\n );\n\n if (props.tooltip === true) {\n return (\n <Widget\n src=\"mob.near/widget/Profile.OverlayTrigger\"\n props={{ accountId, children: inner }}\n />\n );\n }\n if (tooltip) {\n inner = (\n <OverlayTrigger placement=\"auto\" overlay={<Tooltip>{tooltip}</Tooltip>}>\n {inner}\n </OverlayTrigger>\n );\n }\n return (\n <div className=\"d-flex flex-row align-items-center\">\n {inner}\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.BadgesList\"\n props={{\n accountId,\n mode: \"compact\",\n }}\n />\n </div>\n );\n};\n\nreturn ProfileCard(props);\n" }, "devhub.entity.proposal.ComposeComment": { "": "const proposalId = props.id;\nconst draftKey = \"COMMENT_DRAFT\" + proposalId;\nconst draftComment = Storage.privateGet(draftKey);\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 }\n`;\nconst notifyAccountId = props.notifyAccountId;\nconst accountId = context.accountId;\nconst item = props.item;\nconst [allowGetDraft, setAllowGetDraft] = useState(true);\nconst [comment, setComment] = useState(null);\n\nuseEffect(() => {\n if (draftComment && allowGetDraft) {\n setComment(draftComment);\n setAllowGetDraft(false);\n }\n}, [draftComment]);\n\nuseEffect(() => {\n const handler = setTimeout(() => {\n if (comment !== draftComment) Storage.privateSet(draftKey, comment);\n }, 2000);\n\n return () => {\n clearTimeout(handler);\n };\n}, [comment, draftKey]);\n\nif (!accountId) {\n return (\n <div\n style={{\n marginLeft: 10,\n backgroundColor: \"#ECF8FB\",\n border: \"1px solid #E2E6EC\",\n }}\n className=\"d-flex align-items-center gap-1 p-4 rounded-2 flex-wrap flex-md-nowrap\"\n >\n <Link to=\"https://near.org/signup\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"grey-btn\" },\n label: \"Sign up\",\n }}\n />\n </Link>\n <div className=\"fw-bold\">to join this conversation.</div>\n <div>Already have an account?</div>\n <a className=\"text-decoration-underline\" href=\"https://near.org/signin\">\n Log in to comment\n </a>\n </div>\n );\n}\n\nfunction extractMentions(text) {\n const mentionRegex =\n /@((?:(?:[a-z\\d]+[-_])*[a-z\\d]+\\.)*(?:[a-z\\d]+[-_])*[a-z\\d]+)/gi;\n mentionRegex.lastIndex = 0;\n const accountIds = new Set();\n for (const match of text.matchAll(mentionRegex)) {\n if (\n !/[\\w`]/.test(match.input.charAt(match.index - 1)) &&\n !/[/\\w`]/.test(match.input.charAt(match.index + match[0].length)) &&\n match[1].length >= 2 &&\n match[1].length <= 64\n ) {\n accountIds.add(match[1].toLowerCase());\n }\n }\n return [...accountIds];\n}\n\nfunction extractTagNotifications(text, item) {\n return extractMentions(text || \"\")\n .filter((accountId) => accountId !== context.accountId)\n .map((accountId) => ({\n key: accountId,\n value: {\n type: \"mention\",\n item,\n },\n }));\n}\n\nfunction composeData() {\n const data = {\n post: {\n comment: JSON.stringify({\n type: \"md\",\n text: comment,\n item,\n }),\n },\n index: {\n comment: JSON.stringify({\n key: item,\n value: {\n type: \"md\",\n },\n }),\n },\n };\n\n const notifications = extractTagNotifications(comment, {\n type: \"social\",\n path: `${accountId}/post/comment`,\n });\n\n if (notifyAccountId && notifyAccountId !== context.accountId) {\n notifications.push({\n key: notifyAccountId,\n value: {\n type: \"devhub/reply\",\n item,\n proposal: proposalId,\n },\n });\n }\n\n if (notifications.length) {\n data.index.notify = JSON.stringify(\n notifications.length > 1 ? notifications : notifications[0]\n );\n }\n\n Social.set(data, {\n force: true,\n onCommit: () => {\n setComment(\"\");\n },\n onCancel: () => {},\n });\n}\n\nuseEffect(() => {\n if (props.transactionHashes && comment) {\n setComment(\"\");\n }\n}, [props.transactionHashes]);\n\nreturn (\n <div className=\"d-flex gap-2\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: accountId,\n }}\n />\n <div className=\"d-flex flex-column gap-2 w-100\">\n <b className=\"mt-1\">Add a comment</b>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Compose\"}\n props={{\n data: comment,\n onChange: setComment,\n autocompleteEnabled: true,\n placeholder: \"Add your comment here...\",\n height: \"160\",\n embeddCSS: ComposeEmbeddCSS,\n showProposalIdAutoComplete: true,\n }}\n />\n <div className=\"d-flex gap-2 align-content-center justify-content-end\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n label: \"Comment\",\n disabled: !comment,\n classNames: { root: \"green-btn btn-sm\" },\n onClick: () => {\n composeData();\n },\n }}\n />\n </div>\n </div>\n </div>\n);\n" }, "devhub.page.admin.moderatorsTab": { "": "const { accessControlInfo, createEditTeam } = props;\n\nconst { Tile } =\n VM.require(\"devhub.megha19.near/widget/devhub.components.molecule.Tile\") ||\n (() => <></>);\n\nif (!Tile) {\n return <div>Loading...</div>;\n}\n\nconst [editModerators, setEditModerators] = useState(false);\nconst [moderators, setModerators] = useState(\n accessControlInfo.members_list[\"team:moderators\"].children || []\n);\n\nconst handleEditModerators = () => {\n createEditTeam({\n teamName: \"moderators\",\n description:\n \"The moderator group has permissions to edit any posts and apply all labels, including restricted ones.\",\n label: \"*\",\n editPost: true,\n useLabels: true,\n members: moderators,\n contractCall: \"edit_member\",\n });\n};\n\nconst handleCancelModerators = () => {\n setEditModerators(false);\n setModerators(accessControlInfo.members_list[\"team:moderators\"].children);\n};\n\nreturn (\n <>\n <h1>Moderators</h1>\n <div className=\"card-body\">\n <h5>\n The moderator group has permissions to edit any posts and apply all\n labels, including restricted ones.\n </h5>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.PostControls\"}\n props={{\n icon: \"bi bi-gear-wide-connected\",\n className: \"mb-3\",\n\n title: \"Edit members\",\n onClick: () => setEditModerators(!editModerators),\n testId: \"edit-members\",\n }}\n />\n </div>\n <Tile className=\"p-3\">\n {editModerators ? (\n <>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.ListEditor\"\n props={{\n data: {\n maxLength: 100,\n placeholder: \"member.near\",\n prefix: \"member\",\n list: moderators,\n },\n setList: setModerators,\n // Could add a check to see if it is an valid account id.\n validate: (newItem) => true,\n invalidate: () => null,\n }}\n />\n <div\n className={\n \"d-flex align-items-center justify-content-end gap-3 mt-4\"\n }\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: {\n root: \"btn-outline-danger shadow-none border-0\",\n },\n label: \"Cancel\",\n onClick: handleCancelModerators,\n }}\n />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn\" },\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Submit\",\n onClick: handleEditModerators,\n }}\n />\n </div>\n </>\n ) : (\n <>\n <div class=\"pt-4\">Members</div>\n\n {moderators && (\n <div class=\"vstack\">\n {moderators.length ? (\n moderators.map((child) => (\n <Tile className=\"w-25 p-3 m-1\" minHeight={10}>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.ProfileLine`}\n props={{ accountId: child }}\n />\n </Tile>\n ))\n ) : (\n <div>No moderators</div>\n )}\n </div>\n )}\n </>\n )}\n </Tile>\n </>\n);\n" }, "devhub.entity.community.configuration.AddonsConfigurator": { "": "const { getAllAddons } =\n VM.require(\"devhub.megha19.near/widget/core.adapter.devhub-contract\") ||\n (() => {});\n\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nconst availableAddons = getAllAddons() || [];\n\nconst isActive = props.isActive;\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n`;\n\nconst Item = styled.div`\n padding: 10px;\n margin: 5px;\n display: flex;\n align-items: center;\n gap: 10px;\n`;\n\nconst Icon = styled.span`\n margin-right: 10px;\n`;\n\nconst EditableField = styled.input`\n flex: 1;\n`;\n\nconst ToggleButton = styled.input`\n margin-left: 10px;\n`;\n\nconst Table = styled.table`\n width: 100%;\n border-collapse: collapse;\n`;\n\nconst Header = styled.thead`\n background-color: #f0f0f0;\n`;\n\nconst HeaderCell = styled.th`\n padding: 10px;\n text-align: left;\n`;\n\nconst Row = styled.tr``;\n\nconst Cell = styled.td`\n padding: 10px;\n`;\n\nfunction generateRandom6CharUUID() {\n const chars = \"0123456789abcdefghijklmnopqrstuvwxyz\";\n let result = \"\";\n\n for (let i = 0; i < 6; i++) {\n const randomIndex = Math.floor(Math.random() * chars.length);\n result += chars[randomIndex];\n }\n\n return result;\n}\n\nconst AddonItem = ({\n data,\n onUpdate,\n onMove,\n onRemove,\n index,\n isTop,\n isBottom,\n}) => {\n const handleNameChange = (event) => {\n const newName = event.target.value;\n onUpdate({ ...data, display_name: newName });\n };\n\n const handleEnableChange = () => {\n onUpdate({ ...data, enabled: !data.enabled });\n };\n\n const moveItemUp = () => {\n if (!isTop) {\n onMove(index, index - 1);\n }\n };\n\n const moveItemDown = () => {\n if (!isBottom) {\n onMove(index, index + 1);\n }\n };\n\n const removeItem = () => {\n onRemove(data.id);\n };\n\n const addonMatch =\n availableAddons.find((it) => it.id === data.addon_id) ?? null;\n\n return (\n <Row>\n <Cell>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0\" }}>\n <button\n className=\"btn btn-sm btn-secondary rounded-0\"\n onClick={moveItemUp}\n disabled={!isActive || isTop}\n style={{ visibility: isTop && !isBottom ? \"hidden\" : \"visible\" }}\n >\n <i className=\"bi bi-arrow-up\"></i>\n </button>\n <button\n className=\"btn btn-sm btn-secondary rounded-0\"\n onClick={moveItemDown}\n disabled={!isActive || isBottom}\n style={{ visibility: isBottom && !isTop ? \"hidden\" : \"visible\" }}\n >\n <i className=\"bi bi-arrow-down\"></i>\n </button>\n </div>\n </Cell>\n <Cell>\n <div>{addonMatch.title}</div>\n </Cell>\n <Cell>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n label: \" \",\n value: data.display_name,\n onChange: handleNameChange,\n inputProps: {\n min: 3,\n max: 30,\n disabled: !data.enabled || !isActive,\n },\n }}\n />\n </Cell>\n <Cell>\n <div\n className={\n \"d-flex flex-column flex-1 align-items-start justify-content-evenly\"\n }\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.atom.Toggle\"}\n props={{\n value: data.enabled,\n onChange: handleEnableChange,\n disabled: !isActive,\n }}\n />\n </div>\n </Cell>\n <Cell>\n <div style={{ display: \"flex\", gap: \"2px\" }}>\n {isActive && (\n <button className=\"btn btn-outline-danger\" onClick={removeItem}>\n <i className=\"bi bi-trash-fill\" />\n </button>\n )}\n </div>\n </Cell>\n </Row>\n );\n};\n\nfunction arraysAreEqual(arr1, arr2) {\n if (arr1.length !== arr2.length) {\n return false;\n }\n for (let i = 0; i < arr1.length; i++) {\n if (arr1[i] !== arr2[i]) {\n return false;\n }\n }\n return true;\n}\n\nconst AddonsConfigurator = ({ data, onSubmit }) => {\n const [originalList, setOriginalList] = useState(data);\n const [list, setList] = useState(data);\n const [changesMade, setChangesMade] = useState(false);\n\n useEffect(() => {\n setOriginalList(data);\n }, [data]);\n\n const updateItem = (updatedItem) => {\n const updatedList = list.map((item) =>\n item.id === updatedItem.id ? updatedItem : item\n );\n setList(updatedList);\n setChangesMade(!arraysAreEqual(originalList, updatedList));\n };\n\n const moveItem = (fromIndex, toIndex) => {\n const updatedList = [...list];\n const [movedItem] = updatedList.splice(fromIndex, 1);\n updatedList.splice(toIndex, 0, movedItem);\n setList(updatedList);\n setChangesMade(!arraysAreEqual(originalList, updatedList));\n };\n\n const [selectedAddon, setSelectedAddon] = useState(null);\n\n const handleAddItem = () => {\n const newItem = {\n id: generateRandom6CharUUID(),\n addon_id: selectedAddon.id,\n display_name: selectedAddon.title,\n enabled: true,\n parameters: \"{}\",\n };\n const updatedList = [...list, newItem];\n setList(updatedList);\n setChangesMade(!arraysAreEqual(originalList, updatedList));\n setSelectedAddon(null);\n };\n\n const removeItem = (id) => {\n const updatedList = list.filter((item) => item.id !== id);\n setList(updatedList);\n setChangesMade(!arraysAreEqual(originalList, updatedList));\n };\n\n return (\n <Container>\n <p>\n Add or remove custom tabs, which will appear in your community's\n navigation bar.\n <br />\n You can customize them on each page.\n </p>\n {list.length > 0 && (\n <Table>\n <Header>\n <Row>\n <HeaderCell style={{ width: \"30px\" }}>Order</HeaderCell>\n <HeaderCell>Tab Type</HeaderCell>\n <HeaderCell>Tab Name</HeaderCell>\n <HeaderCell style={{ width: \"45px\" }}>Enabled</HeaderCell>\n {isActive && (\n <HeaderCell style={{ width: \"40px\" }}>Actions</HeaderCell>\n )}\n </Row>\n </Header>\n <tbody data-testid=\"addon-table\">\n {list.map((item, index) => (\n <AddonItem\n key={item.id}\n data={item}\n onUpdate={updateItem}\n onMove={moveItem}\n onRemove={removeItem}\n index={index}\n isTop={index === 0}\n isBottom={index === list.length - 1}\n />\n ))}\n </tbody>\n </Table>\n )}\n {isActive && availableAddons && (\n <div className=\"d-flex justify-content-center pt-2\">\n <div className=\"d-flex gap-2 flex-grow-1 px-4\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Select\"}\n props={{\n className: \"flex-grow-1\",\n options: availableAddons.map((addon) => ({\n label: addon.title,\n value: addon.id,\n })),\n value: selectedAddon.id ?? \"\",\n onChange: (e) =>\n setSelectedAddon(\n availableAddons.find((addon) => addon.id === e.target.value)\n ),\n placeholder: \"Select an addon\",\n }}\n />\n <button\n className=\"btn btn-success\"\n onClick={handleAddItem}\n disabled={!selectedAddon}\n >\n <i className=\"bi bi-plus\" />\n </button>\n </div>\n </div>\n )}\n {isActive && (\n <div\n className={\"d-flex align-items-center justify-content-end gap-3 mt-4\"}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-success\" },\n disabled: !changesMade,\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Submit\",\n onClick: () => onSubmit(list),\n }}\n />\n </div>\n )}\n </Container>\n );\n};\n\nreturn AddonsConfigurator(props);\n" }, "devhub.entity.community.Card": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nif (!href) {\n return <></>;\n}\n\nconst Card = styled.div`\n cursor: pointer;\n background-color: white;\n border-radius: 0.5rem;\n padding: 1.5rem;\n gap: 1rem;\n height: 100%;\n min-height: 12rem;\n\n display: flex;\n align-items: center;\n justify-content: flex-start;\n transition: all 300ms;\n box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);\n\n &:hover {\n box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);\n }\n\n img.logo {\n height: 6rem;\n width: 6rem;\n border-radius: 50%;\n\n object-fit: cover;\n }\n\n h3,\n p {\n margin: 0;\n }\n\n h3 {\n font-size: 1.25rem;\n font-weight: 600;\n }\n\n p {\n font-size: 1rem;\n font-weight: 400;\n }\n`;\n\nconst CommunityCard = ({ metadata }) => {\n const { handle, logo_url, name, description } = metadata;\n const link = href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"community\", handle: handle },\n });\n\n return (\n <Link to={link} style={{ all: \"unset\" }}>\n <Card>\n <img className=\"logo\" src={logo_url} />\n\n <div className=\"d-flex flex-column justify-content-center gap-1 w-100\">\n <h3>{name}</h3>\n <p>{description}</p>\n </div>\n </Card>\n </Link>\n );\n};\n\nreturn CommunityCard(props);\n" }, "devhub.entity.post.PostEditor": { "": "const { normalize } = VM.require(\"devhub.megha19.near/widget/core.lib.stringUtils\");\nconst { getDepositAmountForWriteAccess } = VM.require(\n \"devhub.megha19.near/widget/core.lib.common\"\n);\n\ngetDepositAmountForWriteAccess || (getDepositAmountForWriteAccess = () => {});\nnormalize || (normalize = () => {});\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: 384px;\n`;\n\nconst LoadingButtonSpinner = (\n <span\n class=\"submit-post-loading-indicator spinner-border spinner-border-sm\"\n role=\"status\"\n aria-hidden=\"true\"\n ></span>\n);\n\nfunction initLabels() {\n const labels = [];\n if (typeof props.labels === \"string\") {\n labels.push(...props.labels.split(\",\"));\n }\n if (Array.isArray(props.labels)) {\n labels.push(...props.labels);\n }\n if (props.referral) {\n labels.push(`referral:${props.referral}`);\n }\n return labels;\n}\n\nif (!context.accountId) {\n return (\n <CenteredMessage height={\"384px\"}>\n <h2>Please sign in to create a post.</h2>\n </CenteredMessage>\n );\n}\n\nconst userStorageDeposit = Near.view(\n \"social.near\",\n \"storage_balance_of\",\n {\n account_id: context.accountId,\n }\n);\n\nconst cleanDescription = (description) => {\n return description\n ? description.replace(\n /###### Requested amount: .+?\\n###### Requested sponsor: @[^\\s]+\\n/g,\n \"\"\n )\n : description;\n};\n\nconst postTypeOptions = {\n Idea: {\n name: \"Idea\",\n icon: \"bi-lightbulb\",\n description:\n \"Get feedback from the community about a problem, opportunity, or need.\",\n },\n\n Solution: {\n name: \"Solution\",\n icon: \"bi-rocket\",\n description:\n \"Provide a specific proposal or implementation to an idea, optionally requesting funding. If your solution relates to an existing idea, please reply to the original post with a solution.\",\n },\n};\n\nlet fields = {\n Comment: [\"description\"],\n Idea: [\"name\", \"description\"],\n Solution: [\"name\", \"description\", \"fund_raising\"],\n Attestation: [\"name\", \"description\"],\n Sponsorship: [\n \"name\",\n \"description\",\n \"amount\",\n \"sponsorship_token\",\n \"supervisor\",\n ],\n Github: [\"githubLink\", \"name\", \"description\"],\n};\n\nconst isCreatePostPage = props.isCreatePostPage ?? false;\nconst postType = props.postType ?? \"Idea\";\nconst parentId = props.parentId ?? null;\nconst mode = props.mode ?? \"Create\";\nconst labelStrings = initLabels();\nconst [postIdList, setPostIdList] = useState(null); // to show updated post after approve txn\nconst [showPostPage, setShowPostPage] = useState(false); // show newly created post\nconst [postId, setPostId] = useState(props.postId ?? null);\nconst [postData, setPostData] = useState(null); // for capturing edit post change\nconst [isTxnCreated, setCreateTxn] = useState(false);\nconst [isSubmittingTransaction, setIsSubmittingTransaction] = useState(false);\n\nuseEffect(() => {\n if (mode == \"Edit\") {\n const data = Near.view(\"devgovgigs.near\", \"get_post\", {\n post_id: postId,\n });\n if (!postData) {\n setPostData(data);\n }\n if (postData && data && JSON.stringify(postData) !== JSON.stringify(data)) {\n props.setEditorState(false);\n props.setExpandReplies(true);\n setPostData(data);\n }\n } else {\n const postIds = Near.view(\"devgovgigs.near\", \"get_all_post_ids\");\n if (!postIdList) {\n setPostIdList(postIds);\n }\n if (\n isTxnCreated &&\n postIdList?.length > 0 &&\n postIds.length > 0 &&\n postIdList.length !== postIds.length\n ) {\n props.onDraftStateChange(null);\n if (isCreatePostPage) {\n setShowPostPage(true);\n setPostId(postIds[postIds?.length - 1]);\n } else {\n // close editor and expand replies\n props.setEditorState(false);\n props.setExpandReplies(true);\n }\n setPostIdList(postIds);\n }\n }\n});\n\nconst labels = labelStrings.map((s) => {\n return { name: s };\n});\n\ninitState({\n seekingFunding: props.seekingFunding ?? false,\n author_id: context.accountId,\n // Should be a list of objects with field \"name\".\n labels,\n // Should be a list of labels as strings.\n // Both of the label structures should be modified together.\n labelStrings: [],\n postType,\n name: props.name ?? \"\",\n description:\n (props.postType === \"Solution\"\n ? cleanDescription(props.description)\n : props.description) ?? \"\",\n amount: props.amount ?? \"0\",\n token: props.token ?? \"USDT\",\n supervisor: props.supervisor ?? \"neardevdao.near\",\n githubLink: props.githubLink ?? \"\",\n warning: \"\",\n draftStateApplied: false,\n mentionInput: \"\", // text next to @ tag\n mentionsArray: [], // all the mentions in the description\n displayFields: fields[postType],\n});\n\n/* INCLUDE: \"core/lib/autocomplete\" */\nconst autocompleteEnabled = true;\n\nconst AutoComplete = styled.div`\n z-index: 5;\n\n > div > div {\n padding: calc(var(--padding) / 2);\n }\n`;\n\nif (props.transactionHashes) {\n const transaction = useCache(\n () =>\n asyncFetch(\"https://rpc.mainnet.near.org\", {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: \"dontcare\",\n method: \"tx\",\n params: [props.transactionHashes, context.accountId],\n }),\n }).then((res) => res),\n props.transactionHashes + context.accountId,\n { subscribe: false }\n );\n\n if (transaction !== null) {\n const transaction_method_name =\n transaction?.body?.result?.transaction?.actions[0].FunctionCall\n .method_name;\n\n const is_edit_or_add_post_transaction =\n transaction_method_name == \"add_post\" ||\n transaction_method_name == \"edit_post\";\n\n if (is_edit_or_add_post_transaction) {\n props.onDraftStateChange(null);\n }\n\n // show the latest created post to user\n if (transaction_method_name == \"add_post\" && isCreatePostPage) {\n setShowPostPage(true);\n setPostId(postIdList?.[postIdList?.length - 1]);\n }\n }\n}\n\nfunction textareaInputHandler(value) {\n const words = value.split(/\\s+/);\n const allMentiones = words\n .filter((word) => word.startsWith(\"@\"))\n .map((mention) => mention.slice(1));\n const newMentiones = allMentiones.filter(\n (item) => !state.mentionsArray.includes(item)\n );\n\n State.update((lastKnownState) => ({\n ...lastKnownState,\n text: value,\n showAccountAutocomplete: newMentiones?.length > 0,\n mentionsArray: allMentiones,\n mentionInput: newMentiones?.[0] ?? \"\",\n }));\n}\n\nfunction autoCompleteAccountId(id) {\n // to make sure we update the @ at correct index\n let currentIndex = 0;\n const updatedDescription = state.description.replace(\n /(?:^|\\s)(@[^\\s]*)/g,\n (match) => {\n if (currentIndex === state.mentionsArray.indexOf(state.mentionInput)) {\n currentIndex++;\n return ` @${id}`;\n } else {\n currentIndex++;\n return match;\n }\n }\n );\n State.update((lastKnownState) => ({\n ...lastKnownState,\n handler: \"autocompleteSelected\",\n description: updatedDescription,\n showAccountAutocomplete: false,\n }));\n}\n\n/* END_INCLUDE: \"core/lib/autocomplete\" */\n\nif (!state.draftStateApplied && props.draftState) {\n State.update({ ...props.draftState, draftStateApplied: true });\n}\n\nconst typeSwitch = (optionName) => {\n State.update({\n postType: optionName,\n displayFields: fields[optionName],\n });\n};\n\n// This must be outside onClick, because Near.view returns null at first, and when the view call finished, it returns true/false.\n// If checking this inside onClick, 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);\nif (grantNotify === null || userStorageDeposit === null) {\n return;\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};\n\nconst onSubmit = () => {\n setIsSubmittingTransaction(true);\n let labels = state.labelStrings;\n var body = {\n Comment: { description: state.description, comment_version: \"V2\" },\n Idea: {\n name: state.name,\n description: state.description,\n idea_version: \"V1\",\n },\n Solution: {\n name: state.name,\n description: generateDescription(\n state.description,\n state.amount,\n state.token,\n state.supervisor,\n state.seekingFunding\n ),\n solution_version: \"V1\",\n },\n Attestation: {\n name: state.name,\n description: state.description,\n attestation_version: \"V1\",\n },\n Sponsorship: {\n name: state.name,\n description: state.description,\n amount: state.amount,\n sponsorship_token: tokenMapping[state.token],\n supervisor: state.supervisor,\n sponsorship_version: \"V1\",\n },\n Github: {\n name: state.name,\n description: state.description,\n github_version: \"V0\",\n github_link: state.githubLink,\n },\n }[state.postType];\n body[\"post_type\"] = state.postType;\n if (!context.accountId) {\n return;\n }\n let txn = [];\n if (mode == \"Create\") {\n props.onDraftStateChange(\n Object.assign({}, state, { parent_post_id: parentId })\n );\n txn.push({\n contractName: \"devgovgigs.near\",\n methodName: \"add_post\",\n args: {\n parent_id: parentId,\n labels,\n body,\n },\n gas: Big(10).pow(14),\n });\n } else if (mode == \"Edit\") {\n props.onDraftStateChange(\n Object.assign({}, state, { edit_post_id: postId })\n );\n txn.push({\n contractName: \"devgovgigs.near\",\n methodName: \"edit_post\",\n args: {\n id: postId,\n labels,\n body,\n },\n gas: Big(10).pow(14),\n });\n }\n if (mode == \"Create\" || mode == \"Edit\") {\n if (grantNotify === false) {\n txn.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 setCreateTxn(true);\n Near.call(txn);\n }\n};\n\nconst checkLabel = (label) => {\n Near.asyncView(\"devgovgigs.near\", \"is_allowed_to_use_labels\", {\n editor: context.accountId,\n labels: [label],\n }).then((allowed) => {\n if (allowed) {\n State.update({ warning: \"\" });\n } else {\n State.update({\n warning:\n 'The label \"' +\n label +\n '\" is protected and can only be added by moderators',\n });\n return;\n }\n });\n};\n\nconst setLabels = (labels) => {\n labels = labels.map((o) => {\n o.name = normalize(o.name);\n return o;\n });\n if (labels.length < state.labels.length) {\n let oldLabels = new Set(state.labels.map((label) => label.name));\n for (let label of labels) {\n oldLabels.delete(label.name);\n }\n let removed = oldLabels.values().next().value;\n Near.asyncView(\"devgovgigs.near\", \"is_allowed_to_use_labels\", {\n editor: context.accountId,\n labels: [removed],\n }).then((allowed) => {\n if (allowed) {\n let labelStrings = labels.map(({ name }) => name);\n State.update({ labels, labelStrings });\n } else {\n State.update({\n warning:\n 'The label \"' +\n removed +\n '\" is protected and can only be updated by moderators',\n });\n return;\n }\n });\n } else {\n let labelStrings = labels.map((o) => {\n return o.name;\n });\n State.update({ labels, labelStrings });\n }\n};\nconst existingLabelStrings =\n Near.view(\"devgovgigs.near\", \"get_all_allowed_labels\", {\n editor: context.accountId,\n }) ?? [];\nconst existingLabelSet = new Set(existingLabelStrings);\nconst existingLabels = existingLabelStrings\n .filter((it) => it !== \"blog\") // remove blog label so users cannot publish blogs from feed\n .map((s) => {\n return { name: s };\n });\n\nconst labelEditor = (\n <div className=\"col-lg-12 mb-2\">\n <label htmlFor=\"labels\" className=\"fs-6 fw-bold mb-1\">\n Labels\n </label>\n <Typeahead\n multiple\n labelKey=\"name\"\n onInputChange={checkLabel}\n onChange={setLabels}\n options={existingLabels}\n placeholder=\"near.social, widget, NEP, standard, protocol, tool\"\n selected={state.labels}\n positionFixed\n allowNew={(results, props) => {\n return (\n !existingLabelSet.has(props.text) &&\n props.text.toLowerCase() !== \"blog\" && // dont allow adding \"Blog\"\n props.selected.filter((selected) => selected.name === props.text)\n .length == 0 &&\n Near.view(\"devgovgigs.near\", \"is_allowed_to_use_labels\", {\n editor: context.accountId,\n labels: [props.text],\n })\n );\n }}\n />\n </div>\n);\n\nconst githubLinkDiv = (\n <div className=\"col-lg-12 mb-2\">\n Github Issue URL:\n <input\n type=\"text\"\n value={state.githubLink}\n onChange={(event) => State.update({ githubLink: event.target.value })}\n />\n </div>\n);\n\nconst nameDiv = (\n <div className=\"col-lg-12 mb-2\">\n <label htmlFor=\"title\" className=\"fs-6 fw-bold mb-1\">\n Title\n </label>\n <input\n name=\"title\"\n id=\"title\"\n data-testid=\"name-editor\"\n type=\"text\"\n value={state.name}\n onChange={(event) => State.update({ name: event.target.value })}\n />\n </div>\n);\n\nconst amountDiv = (\n <div className=\"col-lg-6 mb-2\">\n Amount:\n <input\n type=\"text\"\n value={state.amount}\n onChange={(event) => State.update({ amount: event.target.value })}\n />\n </div>\n);\n\nconst tokenDiv = (\n <div className=\"col-lg-6 mb-2\">\n Currency\n <select\n onChange={(event) => State.update({ token: event.target.value })}\n class=\"form-select\"\n aria-label=\"Select currency\"\n value={state.token}\n >\n <option value=\"USDT\">USDT</option>\n <option value=\"NEAR\">NEAR</option>\n <option value=\"USDC\">USDC</option>\n </select>\n </div>\n);\n\nconst supervisorDiv = (\n <div className=\"col-lg-6 mb-2\">\n Supervisor:\n <input\n type=\"text\"\n value={state.supervisor}\n onChange={(event) => State.update({ supervisor: event.target.value })}\n />\n </div>\n);\n\nconst callDescriptionDiv = () => {\n return (\n <div className=\"col-lg-12 mb-2\">\n <label htmlFor=\"description\" className=\"fs-6 fw-bold mb-1\">\n Description\n </label>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.MarkdownEditor\"}\n props={{\n data: { handler: state.handler, content: state.description },\n onChange: (content) => {\n State.update({ description: content, handler: \"update\" });\n textareaInputHandler(content);\n },\n }}\n />\n {autocompleteEnabled && state.showAccountAutocomplete && (\n <AutoComplete>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.AccountAutocomplete\"\n props={{\n term: state.mentionInput,\n onSelect: autoCompleteAccountId,\n onClose: () => State.update({ showAccountAutocomplete: false }),\n }}\n />\n </AutoComplete>\n )}\n </div>\n );\n};\n\nconst disclaimer = (\n <p>\n <i>\n * Note, all projects that were granted sponsorships are required to pass\n KYC to receive the funding.\n </i>\n </p>\n);\n\nconst isFundraisingDiv = (\n // This is jank with just btns and not radios. But the radios were glitchy af\n <>\n <div class=\"mb-2\">\n <p class=\"fs-6 fw-bold mb-1\">\n Are you seeking funding for your solution?\n <span class=\"text-muted fw-normal\">(Optional)</span>\n </p>\n <div class=\"form-check form-check-inline\">\n <label class=\"form-check-label\">\n <button\n className=\"btn btn-light p-0\"\n style={{\n backgroundColor: state.seekingFunding ? \"#0C7283\" : \"inherit\",\n color: \"#f3f3f3\",\n border: \"solid #D9D9D9\",\n borderRadius: \"100%\",\n height: \"20px\",\n width: \"20px\",\n }}\n onClick={() => State.update({ seekingFunding: true })}\n />\n Yes\n </label>\n </div>\n <div class=\"form-check form-check-inline\">\n <label class=\"form-check-label\">\n <button\n className=\"btn btn-light p-0\"\n style={{\n backgroundColor: !state.seekingFunding ? \"#0C7283\" : \"inherit\",\n color: \"#f3f3f3\",\n border: \"solid #D9D9D9\",\n borderRadius: \"100%\",\n height: \"20px\",\n width: \"20px\",\n }}\n onClick={() => State.update({ seekingFunding: false })}\n />\n No\n </label>\n </div>\n </div>\n </>\n);\n\nconst fundraisingDiv = (\n <div class=\"d-flex flex-column mb-2\">\n <div className=\"col-lg-6 mb-2\">\n Currency\n <select\n onChange={(event) => State.update({ token: event.target.value })}\n class=\"form-select\"\n aria-label=\"Default select example\"\n value={state.token}\n >\n <option value=\"USDT\">USDT</option>\n <option value=\"NEAR\">NEAR</option>\n <option value=\"USDC\">USDC</option>\n </select>\n </div>\n <div className=\"col-lg-6 mb-2\">\n Requested amount\n <span class=\"text-muted fw-normal\">(Numbers Only)</span>\n <input\n data-testid=\"requested-amount-editor\"\n type=\"number\"\n value={parseInt(state.amount) > 0 ? state.amount : \"\"}\n min={0}\n onChange={(event) => {\n State.update({\n amount: Number(\n event.target.value.toString().replace(/e/g, \"\")\n ).toString(),\n });\n }}\n />\n </div>\n <div className=\"col-lg-6 mb-2\">\n <p class=\"mb-1\">\n Requested sponsor\n <span class=\"text-muted fw-normal\">(Optional)</span>\n </p>\n <p style={{ fontSize: \"13px\" }} class=\"m-0 text-muted fw-light\">\n If you are requesting funding from a specific sponsor, please enter\n their username.\n </p>\n <div class=\"input-group flex-nowrap\">\n <span class=\"input-group-text\" id=\"addon-wrapping\">\n @\n </span>\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"Enter username\"\n value={state.supervisor}\n onChange={(event) => State.update({ supervisor: event.target.value })}\n />\n </div>\n </div>\n </div>\n);\n\nfunction generateDescription(text, amount, token, supervisor, seekingFunding) {\n const fundingText =\n amount > 0 && token ? `###### Requested amount: ${amount} ${token}\\n` : \"\";\n const supervisorText = supervisor\n ? `###### Requested sponsor: @${supervisor}\\n`\n : \"\";\n return seekingFunding ? `${fundingText}${supervisorText}${text}` : text;\n}\n\nconst [tab, setTab] = useState(\"editor\");\n\nconst renamedPostType =\n state.postType == \"Submission\" ? \"Solution\" : state.postType;\n// Below there is a weird code with fields.includes(\"githubLink\") ternary operator.\n// This is to hack around rendering bug of near.social.\n\nreturn (\n <div className=\"d-flex flex-column flex-grow-1 w-100\">\n <div className=\"mx-2 mx-md-5 mb-5\">\n {showPostPage ? (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.post.Post\"}\n props={{\n id: postId,\n expandable: true,\n defaultExpanded: false,\n isInList: true,\n isPreview: false,\n onDraftStateChange: props.onDraftStateChange,\n referral: postId,\n transactionHashes: props.transactionHashes,\n }}\n />\n ) : (\n <div className=\"card\">\n <div className=\"card-header\">\n <div>\n <ul class=\"nav nav-tabs\">\n <li class=\"nav-item\">\n <button\n class={`nav-link ${tab === \"editor\" ? \"active\" : \"\"}`}\n onClick={() => setTab(\"editor\")}\n >\n Editor\n </button>\n </li>\n <li class=\"nav-item\">\n <button\n class={`nav-link ${tab === \"preview\" ? \"active\" : \"\"}`}\n onClick={() => setTab(\"preview\")}\n >\n Preview\n </button>\n </li>\n </ul>\n </div>\n {!isCreatePostPage && tab === \"editor\" && (\n <div className=\"my-3\">\n {mode} {renamedPostType}\n </div>\n )}\n {tab === \"preview\" && <div className=\"my-3\">Post Preview</div>}\n </div>\n <div class=\"card-body\">\n {tab === \"editor\" && (\n <>\n {state.warning && (\n <div\n class=\"alert alert-warning alert-dismissible fade show\"\n role=\"alert\"\n >\n {state.warning}\n <button\n type=\"button\"\n class=\"btn-close\"\n data-bs-dismiss=\"alert\"\n aria-label=\"Close\"\n onClick={() => State.update({ warning: \"\" })}\n ></button>\n </div>\n )}\n {isCreatePostPage && (\n <div>\n <p class=\"card-title fw-bold fs-6\">\n What do you want to create?\n </p>\n <div class=\"d-flex flex-row gap-2\">\n {Object.values(postTypeOptions).map((option) => (\n <button\n className={`btn btn-${\n state.postType === option.name\n ? \"primary\"\n : \"outline-secondary\"\n }`}\n data-testid={`btn-${option.name.toLowerCase()}`}\n key={option.name}\n onClick={() => typeSwitch(option.name)}\n style={\n state.postType === option.name\n ? {\n backgroundColor: \"#0C7283\",\n color: \"#f3f3f3\",\n }\n : null\n }\n type=\"button\"\n >\n <i className={`bi ${option.icon}`} />\n <span>{option.name}</span>\n </button>\n ))}\n </div>\n <p class=\"text-muted w-75 my-1\">\n {postTypeOptions[state.postType].description}\n </p>\n </div>\n )}\n {/* This statement around the githubLinkDiv creates a weird render bug\n where the title renders extra on state change. */}\n {state.displayFields.includes(\"githubLink\") ? (\n <div className=\"row\">\n {state.displayFields.includes(\"githubLink\") &&\n githubLinkDiv}\n {labelEditor}\n {state.displayFields.includes(\"name\") && nameDiv}\n {state.displayFields.includes(\"description\") &&\n callDescriptionDiv()}\n </div>\n ) : (\n <div className=\"row\">\n {labelEditor}\n {state.displayFields.includes(\"name\") && nameDiv}\n {state.displayFields.includes(\"amount\") && amountDiv}\n {state.displayFields.includes(\"sponsorship_token\") &&\n tokenDiv}\n {state.displayFields.includes(\"supervisor\") &&\n supervisorDiv}\n {state.displayFields.includes(\"description\") &&\n callDescriptionDiv()}\n {state.displayFields.includes(\"fund_raising\") &&\n isFundraisingDiv}\n {state.seekingFunding &&\n state.displayFields.includes(\"fund_raising\") &&\n fundraisingDiv}\n </div>\n )}\n\n {disclaimer}\n </>\n )}\n {tab === \"preview\" && (\n <div className=\"mb-2\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.post.Post\"\n props={{\n isPreview: true,\n id: 0, // irrelevant\n post: {\n author_id: state.author_id,\n likes: [],\n snapshot: {\n editor_id: state.editor_id,\n labels: state.labelStrings,\n post_type: postType,\n name: state.name,\n description:\n state.postType == \"Solution\"\n ? generateDescription(\n state.description,\n state.amount,\n state.token,\n state.supervisor,\n state.seekingFunding\n )\n : state.description,\n amount: state.amount,\n sponsorship_token: state.token,\n supervisor: state.supervisor,\n github_link: state.githubLink,\n },\n },\n }}\n />\n </div>\n )}\n\n <>\n <button\n data-testid=\"submit-create-post\"\n style={{\n width: \"7rem\",\n backgroundColor: \"#0C7283\",\n color: \"#f3f3f3\",\n }}\n disabled={\n (state.seekingFunding &&\n (!state.amount || state.amount < 1)) ||\n (isCreatePostPage &&\n (state.name === \"\" || state.description === \"\"))\n }\n className=\"btn btn-light mb-2 p-3\"\n onClick={onSubmit}\n disabled={isSubmittingTransaction}\n >\n {isSubmittingTransaction ? LoadingButtonSpinner : <></>}\n Submit\n </button>\n {!isCreatePostPage && (\n <button\n style={{\n width: \"7rem\",\n backgroundColor: \"#fff\",\n color: \"#000\",\n }}\n className=\"btn btn-light mb-2 p-3\"\n onClick={() => props.setEditorState(false)}\n >\n Cancel\n </button>\n )}\n </>\n </div>\n </div>\n )}\n </div>\n </div>\n);\n" }, "devhub.entity.proposal.Feed": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nif (!href) {\n return <p>Loading modules...</p>;\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 .green-btn {\n background-color: #04a46e !important;\n border: none;\n color: white;\n\n &:active {\n color: white;\n }\n }\n\n @media screen and (max-width: 768px) {\n .green-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\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: `devhub.near/post/main`,\n blockHeight: blockHeight,\n };\n\n return (\n <a\n href={href({\n widgetSrc: \"devhub.megha19.near/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.megha19.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 <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.CategoryTag\"}\n props={{\n category: proposal.category,\n }}\n />\n </div>\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.megha19.near/widget/devhub.entity.proposal.LikeButton\"\n props={{\n item,\n proposalId: proposal.id,\n notifyAccountId: accountId,\n }}\n />\n\n <Widget\n src={\"devhub.megha19.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.megha19.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(\"devhub.near\", \"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 cachedItems: {},\n author: \"\",\n stage: \"\",\n sort: \"\",\n category: \"\",\n input: \"\",\n loading: false,\n loadingMore: false,\n aggregatedCount: 0,\n currentlyDisplaying: 0,\n });\n\n const queryName =\n \"thomasguntenaar_near_devhub_proposals_quebec_proposals_with_latest_snapshot\";\n const 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 author_id\n block_height\n name\n category\n summary\n editor_id\n proposal_id\n ts\n timeline\n views\n }\n ${queryName}_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\": `thomasguntenaar_near` },\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 where = { category: { _eq: state.category }, ...where };\n }\n\n if (state.stage) {\n // timeline is stored as jsonb\n where = {\n timeline: { _cast: { String: { _ilike: `%${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 = { description: { _ilike: `%${text}%` }, ...where };\n }\n }\n\n return where;\n };\n\n const buildOrderByClause = () => {\n /**\n * TODO\n * Most commented -> edit contract and indexer\n * Unanswered -> 0 comments\n */\n };\n\n const makeMoreItems = () => {\n if (state.aggregatedCount <= state.currentlyDisplaying) return;\n fetchProposals(state.data.length);\n };\n\n const fetchProposals = (offset) => {\n if (!offset) {\n offset = 0;\n }\n if (state.loading) return;\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 =\n result.body.data\n .thomasguntenaar_near_devhub_proposals_quebec_proposals_with_latest_snapshot;\n const totalResult =\n result.body.data\n .thomasguntenaar_near_devhub_proposals_quebec_proposals_with_latest_snapshot_aggregate;\n State.update({ aggregatedCount: totalResult.aggregate.count });\n // Parse timeline\n fetchBlockHeights(data, offset);\n }\n }\n });\n };\n\n const renderItem = (item, index) => (\n <div\n key={item.proposal_id}\n className={\n (index !== state.data.length - 1 && \"border-bottom \") + index === 0 &&\n \" rounded-top-2\"\n }\n >\n <FeedItem proposal={item} index={index} />\n </div>\n );\n const cachedRenderItem = (item, index) => {\n if (props.term) {\n return renderItem(item, {\n searchKeywords: [props.term],\n });\n }\n\n const key = JSON.stringify(item);\n\n if (!(key in state.cachedItems)) {\n state.cachedItems[key] = renderItem(item, index);\n State.update();\n }\n return state.cachedItems[key];\n };\n\n useEffect(() => {\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 });\n } else {\n State.update({\n data,\n currentlyDisplaying: data.length,\n loading: false,\n });\n }\n });\n };\n\n const loader = (\n <div className=\"d-flex justify-content-center align-items-center w-100\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Spinner\"}\n />\n </div>\n );\n\n const renderedItems = state.data ? state.data.map(cachedRenderItem) : null;\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 DevDAO 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.megha19.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 fetchProposals();\n },\n onEnter: () => {\n fetchProposals();\n },\n }}\n />\n <Widget\n src={\"devhub.megha19.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 <Widget\n src={\n \"devhub.megha19.near/widget/devhub.feature.proposal-search.by-category\"\n }\n props={{\n onStateChange: (select) => {\n State.update({ category: select.value });\n },\n }}\n />\n <Widget\n src={\n \"devhub.megha19.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.megha19.near/widget/devhub.feature.proposal-search.by-author\"\n }\n props={{\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: \"devhub.megha19.near/widget/app\",\n params: { page: \"create-proposal\" },\n })}\n >\n <Widget\n src={\"devhub.megha19.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 New Proposal\n </div>\n ),\n classNames: { root: \"green-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 <div className=\"text-muted bg-grey text-sm mt-2 p-3 rounded-3\">\n <p className=\"d-flex gap-3 align-items-center mb-0\">\n <div>\n <i class=\"bi bi-info-circle\"></i>\n </div>\n <div>\n <span className=\"fw-bold\">\n Welcome to\n <a\n href=\"https://near.social/devhub.near/widget/app?page=community&handle=developer-dao&tab=overview\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n DevDAO’s New Proposal Feed!\n </a>\n </span>\n This dedicated space replaces the\n <a\n href=\"https://near.org/devhub.near/widget/app?page=feed\"\n className=\"text-decoration-underline no-space\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n old activity feed\n </a>\n , making it easier to submit and track funding requests from\n DevDAO, the primary organization behind DevHub. To submit a\n formal proposal, click New Proposal. See our{\" \"}\n <a\n href=\"https://near.org/devhub.near/widget/app?page=community&handle=developer-dao&tab=funding\"\n className=\"text-decoration-underline no-space\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n guidelines\n </a>\n for details. For discussions and brainstorming, please\n utilize the relevant{\" \"}\n <a\n href=\"https://near.org/devhub.near/widget/app?page=communities\"\n className=\"text-decoration-underline no-space\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n communities\n </a>\n .\n </div>\n </p>\n </div>\n <div className=\"mt-4 border rounded-2\">\n {state.data.length > 0 ? (\n <InfiniteScroll\n pageStart={0}\n loadMore={makeMoreItems}\n hasMore={state.aggregatedCount > state.data.length}\n loader={loader}\n useWindow={false}\n threshold={100}\n >\n {renderedItems}\n </InfiniteScroll>\n ) : (\n loader\n )}\n </div>\n </div>\n </div>\n )}\n </div>\n </Container>\n );\n};\n\nreturn FeedPage(props);\n" }, "devhub.page.home": { "": "const HomeSections = [\"hero\", \"explore\", \"connect\", \"participate\", \"support\"];\n\nreturn (\n <>\n {HomeSections.map((it) => (\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.island.${it}`}\n props={{ ...props }}\n />\n ))}\n </>\n);\n" }, "devhub.entity.addon.kanban.Viewer": { "": "const Struct = VM.require(\"devhub.megha19.near/widget/core.lib.struct\");\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nif (!Struct) {\n return <p>Loading modules...</p>;\n}\n\nhref || (href = () => {});\n\nconst { data, handle, permissions } = props;\n\nif (!data || !data?.metadata) {\n return (\n <div\n className=\"d-flex flex-column align-items-center justify-content-center gap-4\"\n style={{ height: 384 }}\n >\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n Please add configuration for your board.\n </h5>\n </div>\n );\n}\n\nreturn (\n <Widget\n src={`devhub.megha19.near/widget/devhub.entity.addon.${data.metadata.type}`}\n props={{\n ...data,\n isSynced: true,\n permissions,\n }}\n />\n);\n" }, "core.adapter.devhub-contract": { "": "function getRootMembers() {\n return Near.view(\"devhub.near\", \"get_root_members\") ?? null;\n}\n\nfunction removeMember(member) {\n return Near.call(\"devhub.near\", \"remove_member\", { member });\n}\n\nfunction hasModerator({ account_id }) {\n return (\n Near.view(\"devhub.near\", \"has_moderator\", { account_id }) ??\n null\n );\n}\n\nfunction createCommunity({ inputs }) {\n return Near.call(\n \"devhub.near\",\n \"create_community\",\n { inputs },\n 250000000000000, // gas (250Tgas)\n Big(4) * Big(10).pow(24) // deposit (4N)\n );\n}\n\nfunction getCommunity({ handle }) {\n return (\n Near.view(\"devhub.near\", \"get_community\", { handle }) ?? null\n );\n}\n\nfunction getFeaturedCommunities() {\n return Near.view(\"devgovgigs.near\", \"get_featured_communities\") ?? null;\n}\n\nfunction setFeaturedCommunities({ handles }) {\n return Near.call(\"devhub.near\", \"set_featured_communities\", {\n handles,\n });\n}\n\nfunction getAccountCommunityPermissions({ account_id, community_handle }) {\n return (\n Near.view(\"devhub.near\", \"get_account_community_permissions\", {\n account_id,\n community_handle,\n }) ?? null\n );\n}\n\nfunction updateCommunity({ handle, community }) {\n return Near.call(\"devhub.near\", \"update_community\", {\n handle,\n community,\n });\n}\n\nfunction deleteCommunity({ handle }) {\n return Near.call(\"devhub.near\", \"delete_community\", { handle });\n}\n\n/**\n * Sets all addons, for configurating tabs\n */\nfunction setCommunityAddons({ handle, addons }) {\n return Near.call(\"devhub.near\", \"set_community_addons\", {\n handle,\n addons,\n });\n}\n\n/**\n * Sets specific addon, for configuring params\n */\nfunction setCommunityAddon({ handle, addon }) {\n return Near.call(\"devhub.near\", \"set_community_addon\", {\n handle,\n community_addon: addon,\n });\n}\n\n/**\n * Gets all available addons, these are controlled by devhub moderators\n */\nfunction getAllAddons() {\n return Near.view(\"devhub.near\", \"get_all_addons\") ?? null;\n}\n\nfunction getAccessControlInfo() {\n return (\n Near.view(\"devhub.near\", \"get_access_control_info\") ?? null\n );\n}\n\nfunction getAllAuthors() {\n return Near.view(\"devgovgigs.near\", \"get_all_authors\") ?? null;\n}\n\nfunction getAllCommunitiesMetadata() {\n return (\n Near.view(\"devhub.near\", \"get_all_communities_metadata\") ?? null\n );\n}\n\nfunction getSocialWithBlockHeight(data) {\n return Near.view(\"social.near\", \"get\", data) ?? null;\n}\n\nfunction getAllLabels() {\n return Near.view(\"devgovgigs.near\", \"get_all_labels\") ?? null;\n}\n\nfunction getPost({ post_id }) {\n return Near.view(\"devgovgigs.near\", \"get_post\", { post_id }) ?? null;\n}\n\nfunction getPostsByAuthor({ author }) {\n return (\n Near.view(\"devgovgigs.near\", \"get_posts_by_author\", { author }) ??\n null\n );\n}\n\nfunction getPostsByLabel({ label }) {\n return (\n Near.view(\"devgovgigs.near\", \"get_posts_by_label\", {\n label,\n }) ?? null\n );\n}\n\nfunction setCommunitySocialDB({ handle, data }) {\n return (\n Near.call(\"devhub.near\", \"set_community_socialdb\", {\n handle,\n data,\n }) ?? null\n );\n}\n\nfunction createDiscussion({ handle, data }) {\n return (\n Near.call(\"devhub.near\", \"create_discussion\", {\n handle,\n data,\n }) ?? null\n );\n}\n\nfunction useQuery(name, params) {\n const initialState = { data: null, error: null, isLoading: true };\n\n const cacheState = useCache(\n () =>\n Near.asyncView(\n \"devgovgigs.near\",\n [\"get\", name].join(\"_\"),\n params ?? {}\n )\n .then((response) => ({\n ...initialState,\n data: response ?? null,\n isLoading: false,\n }))\n .catch((error) => ({\n ...initialState,\n error: props?.error ?? error,\n isLoading: false,\n })),\n\n JSON.stringify({ name, params }),\n { subscribe: false } // NOTE: I'm turning off subscribe to stop the constant re-rendering\n );\n\n return cacheState === null ? initialState : cacheState;\n}\n\nreturn {\n getRootMembers,\n removeMember,\n hasModerator,\n createCommunity,\n getCommunity,\n getFeaturedCommunities,\n setFeaturedCommunities,\n getAccountCommunityPermissions,\n updateCommunity,\n deleteCommunity,\n setCommunityAddons,\n setCommunityAddon,\n getAccessControlInfo,\n getAllAuthors,\n getAllCommunitiesMetadata,\n getAllAddons,\n getAllLabels,\n getPost,\n getPostsByAuthor,\n getPostsByLabel,\n setCommunitySocialDB,\n useQuery,\n};\n" }, "devhub.notification.Right": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nreturn props.proposal === undefined ? (\n \"Loading ...\"\n) : (\n <>\n <a\n className=\"btn btn-outline-dark\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"proposal\",\n id: props.proposal,\n },\n })}\n >\n View DevHub proposal\n </a>\n </>\n);\n" }, "devhub.page.communities": { "": "const { getAllCommunitiesMetadata, createCommunity } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nif (!getAllCommunitiesMetadata || !createCommunity) {\n return <p>Loading modules...</p>;\n}\n\nconst onCommunitySubmit = (inputs) =>\n createCommunity({\n inputs: {\n ...inputs,\n\n bio_markdown: [\n \"This is a sample text about your community.\",\n \"You can change it on the community configuration page.\",\n ].join(\"\\n\"),\n\n logo_url:\n \"https://ipfs.near.social/ipfs/bafkreibysr2mkwhb4j36h2t7mqwhynqdy4vzjfygfkfg65kuspd2bawauu\",\n\n banner_url:\n \"https://ipfs.near.social/ipfs/bafkreic4xgorjt6ha5z4s5e3hscjqrowe5ahd7hlfc5p4hb6kdfp6prgy4\",\n },\n });\n\nconst [showSpawner, setShowSpawner] = useState(false);\n\nconst [searchKey, setSearchKey] = useState(\"\");\nconst [sort, setSort] = useState(\"\");\n\nconst communitiesMetadata = getAllCommunitiesMetadata();\n\nconst SortedAndFiltered = (searchKey, sortBy) => {\n let communities = (communitiesMetadata ?? []).reverse();\n\n let filtered = [...communities];\n if (searchKey !== \"\") {\n filtered = communities.filter((community) =>\n community.name.toLowerCase().includes(searchKey.toLowerCase())\n );\n }\n\n let sortedCommunities = [...filtered];\n if (sortBy !== \"\") {\n sortedCommunities.sort((a, b) => {\n let nameA = a.name.toLowerCase();\n let nameB = b.name.toLowerCase();\n\n if (nameA < nameB) {\n return -1;\n }\n if (nameA > nameB) {\n return 1;\n }\n return 0;\n });\n\n if (sortBy === \"z-a\") {\n sortedCommunities.reverse();\n }\n }\n\n return sortedCommunities;\n};\n\nconst CardGrid = styled.div`\n width: 100%;\n height: 100%;\n\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 3rem;\n\n @media screen and (max-width: 992px) {\n grid-template-columns: repeat(2, 1fr);\n }\n\n @media screen and (max-width: 768px) {\n display: flex;\n flex-direction: column;\n gap: 2rem;\n }\n`;\n\nconst InputContainer = styled.div`\n display: flex;\n flex-direction: row;\n position: relative;\n width: 100%;\n`;\n\nconst StyledDropdown = styled.div`\n button {\n text-align: left;\n\n &::after {\n position: absolute;\n right: 8px;\n top: 45%;\n transform: translateX(-50%);\n }\n }\n`;\n\nreturn (\n <div className=\"w-100\">\n <Widget src={`devhub.megha19.near/widget/devhub.components.island.banner`} />\n <div style={{ background: \"#f4f4f4\" }}>\n <div\n className=\"d-flex justify-content-between p-4\"\n style={{ backgroundColor: \"\" }}\n >\n <div className=\"d-flex flex-column gap-3 w-100\">\n <h1\n className=\"m-0 fs-4\"\n style={{ color: \"#555555\", fontSize: \"1.5rem\" }}\n >\n Communities\n </h1>\n\n <div className=\"d-flex col-12 flex-column flex-sm-row gap-4 justify-content-between align-items-center\">\n <div className=\"d-flex flex-column flex-sm-row align-items-center gap-4 col-12 col-sm-6\">\n <InputContainer className=\"border rounded-2\">\n <div className=\"position-absolute d-flex ps-3 flex-column h-100 justify-center\">\n <i class=\"bi bi-search m-auto\"></i>\n </div>\n <input\n type=\"search\"\n className=\"ps-5 form-control border border-0\"\n value={searchKey ?? \"\"}\n onChange={(e) => setSearchKey(e.target.value)}\n placeholder={props.placeholder ?? `Search by name`}\n />\n </InputContainer>\n <div class=\"dropdown w-100\">\n <StyledDropdown>\n <button\n class=\"btn dropdown-toggle border rounded-2 bg-white w-100\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n >\n Sort: {sort?.toUpperCase() || \"Latest\"}\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end dropdown-menu-lg-start px-2 shadow\">\n <li\n onClick={() => setSort(\"\")}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n >\n Latest\n </li>\n <li\n onClick={() => setSort(\"a-z\")}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n >\n A-Z\n </li>\n <li\n onClick={() => setSort(\"z-a\")}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n >\n Z-A\n </li>\n </ul>\n </StyledDropdown>\n </div>\n </div>\n {context.accountId && (\n <div className=\"d-flex flex-column justify-content-center align-self-end\">\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.components.molecule.CommunityControl\"\n }\n props={{\n title: \"Community\",\n onClick: () => setShowSpawner(!showSpawner),\n }}\n />\n </div>\n )}\n </div>\n </div>\n </div>\n <div className=\"d-flex flex-wrap align-content-start gap-4 py-2 px-4 w-100 h-100\">\n {showSpawner && (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.community.Spawner\"\n props={{\n data: null,\n onSubmit: onCommunitySubmit,\n onCancel: () => setShowSpawner(false),\n }}\n />\n )}\n <CardGrid>\n {searchKey === \"\" && sort === \"\"\n ? (communitiesMetadata ?? []).reverse().map((communityMetadata) => (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.community.Card\"\n props={{\n format: \"small\",\n isBannerEnabled: false,\n metadata: communityMetadata,\n }}\n />\n ))\n : SortedAndFiltered(searchKey, sort).map((communityMetadata) => (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.community.Card\"\n props={{\n format: \"small\",\n isBannerEnabled: false,\n metadata: communityMetadata,\n }}\n />\n ))}\n </CardGrid>\n </div>\n </div>\n </div>\n);\n" }, "devhub.entity.addon.blog.Card": { "": "function Card({ data }) {\n const { category, title, subtitle, date } = data;\n\n const Container = styled.div`\n min-height: 12.5rem;\n height: 100%;\n display: flex;\n padding: 1rem;\n flex-direction: column;\n align-items: flex-start;\n gap: 1rem;\n flex-shrink: 0;\n\n border-radius: 1rem;\n border: 1px solid rgba(129, 129, 129, 0.3);\n background: #fffefe;\n\n h5 {\n margin: 0;\n color: #151515;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 110%; /* 39.6px */\n }\n\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: 1rem;\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: 16px;\n font-style: normal;\n font-weight: 400;\n line-height: 20px; /* 125% */\n margin-top: auto;\n }\n\n p {\n margin: 0;\n color: #151515;\n font-size: 1rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n }\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 <Container id={`blog-card-${title}`}>\n {category && <span className=\"category\">{category}</span>}\n <h5>{title}</h5>\n <p>{subtitle}</p>\n <span className=\"date\">{formattedDate}</span>\n </Container>\n );\n}\n\nreturn { Card };\n" }, "DevGov.Notification.Item.Right": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nreturn props.post === undefined ? (\n \"Loading ...\"\n) : (\n <>\n <a\n className=\"btn btn-outline-dark\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"post\",\n id: props.post,\n },\n })}\n >\n View DevHub post\n </a>\n </>\n);\n" }, "DevGov.Notification.Item.Left": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nif (!props.type) {\n return \"Loading ...\";\n}\n\nconst type = props.type.split(\"/\")[1];\nreturn props.type ? (\n <>\n {type == \"like\"\n ? \"liked your\"\n : type == \"reply\"\n ? \"replied to your\"\n : type == \"edit\"\n ? \"edited your\"\n : type == \"mention\"\n ? \"mentioned you in their\"\n : \"???\"}{\" \"}\n <a\n className=\"fw-bold text-muted\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"post\",\n id: props.post,\n },\n })}\n >\n DevHub post\n </a>\n </>\n) : (\n \"Loading ...\"\n);\n" }, "devhub.entity.addon.kanban.Configurator": { "": "const Struct = VM.require(\"devhub.megha19.near/widget/core.lib.struct\");\n\nif (!Struct) {\n return <p>Loading modules...</p>;\n}\nconst { useQuery } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\nconst { uuid, withUUIDIndex } = VM.require(\n \"devhub.megha19.near/widget/core.lib.uuid\"\n);\n\nuuid || (uuid = () => {});\nwithUUIDIndex || (withUUIDIndex = () => {});\nuseQuery || (useQuery = () => {});\n\nconst AttractableDiv = styled.div`\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\nconst settings = {\n maxColumnsNumber: 10,\n};\n\nconst KanbanPostBoardBasicInfoSchema = {\n title: { label: \"Title\", order: 1, placeholder: \"Enter board title\" },\n description: {\n label: \"Description\",\n order: 2,\n placeholder: \"Enter board description\",\n },\n};\n\nconst KanbanPostBoardTicketFeaturesSchema = {\n author: { label: \"Author\" },\n like_count: { label: \"Likes\" },\n approved_sponsorship_value: { label: \"Funding amount\" },\n sponsorship_supervisor: { label: \"Supervisor/Sponser\" },\n tags: { label: \"Tags\" },\n type: { label: \"Post type\" },\n};\n\nconst KanbanPostBoardDefaults = {\n metadata: {\n id: uuid(),\n type: \"kanban.post_board\",\n title: \"\",\n description: \"\",\n ticket: {\n type: \"kanban.post_ticket\",\n features: {\n author: true,\n like_count: true,\n approved_sponsorship_value: true,\n sponsorship_supervisor: true,\n tags: true,\n type: true,\n },\n sortBy: \"\",\n },\n },\n payload: {\n columns: {},\n },\n};\n\nconst toMigrated = ({ config, metadata, payload }) => ({\n metadata: {\n ...KanbanPostBoardDefaults.metadata,\n ...metadata,\n ticket: {\n ...KanbanPostBoardDefaults.metadata.ticket,\n ...metadata.ticket,\n features: {\n ...KanbanPostBoardDefaults.metadata.ticket.features,\n ...metadata.ticket.features,\n },\n },\n },\n payload: {\n ...KanbanPostBoardDefaults.payload,\n ...payload,\n ...config,\n },\n});\n\nconst sortByOptions = [\n { label: \"None\", value: \"none\" },\n { label: \"Amount: High to Low\", value: \"descending-amount\" },\n { label: \"Amount: Low to High\", value: \"ascending-amount\" },\n { label: \"Date: Newest to Oldest\", value: \"descending-date\" },\n { label: \"Date: Oldest to Newest\", value: \"ascending-date\" },\n { label: \"Author: A-Z\", value: \"ascending-author\" },\n { label: \"Author: Z-A\", value: \"descending-author\" },\n { label: \"Sponsor/Supervisor: A-Z\", value: \"ascending-sponsor\" },\n { label: \"Sponsor/Supervisor: Z-A\", value: \"descending-sponsor\" },\n { label: \"Most Likes\", value: \"descending-likes\" },\n { label: \"Fewest Likes\", value: \"ascending-likes\" },\n];\n\nconst KanbanViewConfigurator = ({ handle, data, permissions, onSubmit }) => {\n const tags = useCache(\n () =>\n Near.asyncView(\"devhub.near\", \"get_all_labels\").then(\n (res) => res\n ),\n handle,\n {\n subscribe: false,\n }\n );\n\n if (!data) {\n return (\n <div class=\"alert alert-danger\" role=\"alert\">\n Loading...\n </div>\n );\n }\n const initialFormState = Struct.pick(\n data.metadata === undefined ? {} : toMigrated(data),\n [\"metadata\", \"payload\"]\n );\n\n const [formState, setForm] = useState(initialFormState);\n const [showPreview, setPreview] = useState(false);\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 ?? {},\n path,\n (node) => transformFn(node)\n );\n setForm((prevFormState) => ({\n ...prevFormState,\n ...updatedValues,\n }));\n };\n\n const formReset = () => {\n setForm(initialFormState);\n };\n\n const newViewInit = () => {\n setForm(KanbanPostBoardDefaults);\n };\n\n const columnsCreateNew = ({ lastKnownValue }) =>\n Object.keys(lastKnownValue).length < settings.maxColumnsNumber\n ? {\n ...(lastKnownValue ?? {}),\n ...withUUIDIndex({ tag: \"\", title: \"New column\", description: \"\" }),\n }\n : lastKnownValue;\n\n const columnsDeleteById =\n (id) =>\n ({ lastKnownValue }) =>\n Object.fromEntries(\n Object.entries(lastKnownValue).filter(([columnId]) => columnId !== id)\n );\n\n const onCancel = () => {\n formReset();\n };\n\n const onSave = () => onSubmit(formState);\n\n const formElement = (\n <div className=\"card p-2\">\n <div className=\"card-body d-flex flex-column gap-3\">\n <div className=\"d-flex flex-column flex-lg-row align-items-stretch w-100\">\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.organism.Configurator`}\n props={{\n heading: \"Basic information\",\n externalState: formState.metadata,\n isActive: true,\n isEmbedded: true,\n isUnlocked: permissions.can_configure,\n onChange: formUpdate({ path: [\"metadata\"] }),\n schema: KanbanPostBoardBasicInfoSchema,\n hideSubmitBtn: true,\n }}\n />\n </div>\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2 flex-grow-1\">\n <span\n className=\"d-flex justify-content-between align-items-center gap-3 w-100\"\n style={{ fontWeight: 500, fontSize: \"16\" }}\n >\n Fields to display\n </span>\n <div>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.organism.Configurator`}\n props={{\n heading: \"Card fields\",\n classNames: { root: \"w-auto h-auto\" },\n externalState: formState.metadata.ticket.features,\n isActive: true,\n isEmbedded: true,\n isUnlocked: permissions.can_configure,\n onChange: formUpdate({\n path: [\"metadata\", \"ticket\", \"features\"],\n }),\n schema: KanbanPostBoardTicketFeaturesSchema,\n style: { minWidth: \"36%\" },\n hideSubmitBtn: true,\n }}\n />\n </div>\n </div>\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2 flex-grow-1\">\n <span\n className=\"d-flex justify-content-between align-items-center gap-3 w-100\"\n style={{ fontWeight: 500, fontSize: \"16\" }}\n >\n Sort by\n </span>\n <div>\n <div className=\"input-group\">\n <select\n className=\"form-select border border-1\"\n value={formState.metadata.ticket.sortBy}\n onChange={formUpdate({\n path: [\"metadata\", \"ticket\", \"sortBy\"],\n })}\n aria-label={label}\n >\n <option value=\"none\" disabled hidden>\n None\n </option>\n {sortByOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n </div>\n\n <div className=\"d-flex align-items-center justify-content-between w-100 mb-1\">\n <span className=\"d-inline-flex gap-2 m-0\">\n <i className=\"bi bi-list-task\" />\n <span>{`Columns ( max. ${settings.maxColumnsNumber} )`}</span>\n </span>\n </div>\n\n <div className=\"d-flex flex-column align-items-center gap-4 w-100\">\n {Object.values(formState.payload.columns ?? {}).map(\n ({ id, description, tag, title }) => (\n <AttractableDiv\n className=\"d-flex gap-3 rounded-4 border p-3 w-100\"\n key={`column-${id}-configurator`}\n >\n <div className=\"d-flex flex-column gap-1 w-100\">\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n key: `column-${id}-title`,\n label: \"Title\",\n onChange: formUpdate({\n path: [\"payload\", \"columns\", id, \"title\"],\n }),\n placeholder: \"Enter column title\",\n value: title,\n }}\n />\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n key: `column-${id}-description`,\n label: \"Description\",\n onChange: formUpdate({\n path: [\"payload\", \"columns\", id, \"description\"],\n }),\n placeholder: \"Enter a brief description for the column\",\n value: description,\n }}\n />\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2 flex-grow-1\">\n <span className=\"d-flex justify-content-between align-items-center gap-3 w-100\">\n Enter a tag to filter posts in this column\n </span>\n <div className=\"w-100\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.feature.post-search.by-tag\"\n props={{\n tag: tag,\n onTagSearch: formUpdate({\n path: [\"payload\", \"columns\", id, \"tag\"],\n }),\n }}\n />\n </div>\n </div>\n </div>\n\n <div\n className=\"d-flex flex-column gap-3 border-start p-3 pe-0\"\n style={{ marginTop: -16, marginBottom: -16 }}\n >\n <button\n className=\"btn btn-outline-danger\"\n onClick={formUpdate({\n path: [\"payload\", \"columns\"],\n via: columnsDeleteById(id),\n })}\n title=\"Delete column\"\n >\n <i className=\"bi bi-trash-fill\" />\n </button>\n </div>\n </AttractableDiv>\n )\n )}\n </div>\n <div className=\"d-flex gap-3 justify-content-between w-100 mt-2 flex-wrap flex-sm-nowrap\">\n <div style={{ flex: \"none\" }}>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"btn-sm btn-outline-secondary\",\n },\n label: \"New column\",\n disabled:\n Object.keys(formState.payload.columns).length >=\n settings.maxColumnsNumber,\n icon: { type: \"bootstrap_icon\", variant: \"bi-plus-lg\" },\n onClick: formUpdate({\n path: [\"payload\", \"columns\"],\n via: columnsCreateNew,\n }),\n }}\n />\n </div>\n <div className=\"d-flex gap-3 justify-content-end w-100\">\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"d-flex btn btn-outline-danger shadow-none border-0\",\n },\n isHidden: typeof onCancel !== \"function\",\n label: \"Cancel\",\n onClick: onCancel,\n }}\n />\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: { root: \"btn btn-success\" },\n disabled: form.isSynced,\n icon: {\n type: \"svg_icon\",\n variant: \"floppy_drive\",\n width: 14,\n height: 14,\n },\n isHidden: typeof onSave !== \"function\",\n label: \"Save\",\n onClick: onSave,\n }}\n />\n </div>\n </div>\n </div>\n </div>\n );\n\n return (\n <div\n className=\"d-flex flex-column gap-4 w-100\"\n style={{ maxWidth: \"100%\" }}\n >\n <ul className=\"nav nav-tabs\" id=\"editPreviewTabs\" role=\"tablist\">\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className={`nav-link ${!showPreview ? \"active\" : \"\"}`}\n id=\"edit-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#edit\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"edit\"\n aria-selected=\"true\"\n onClick={() => setPreview(false)}\n >\n Edit\n </button>\n </li>\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className={`nav-link ${showPreview ? \"active\" : \"\"}`}\n id=\"preview-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#preview\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"preview\"\n aria-selected=\"false\"\n onClick={() => setPreview(true)}\n >\n Preview\n </button>\n </li>\n </ul>\n {showPreview ? (\n <div>\n <Widget\n src={`devhub.megha19.near/widget/devhub.entity.addon.kanban.Viewer`}\n props={{\n data: formState,\n }}\n />\n </div>\n ) : (\n <div className={[\"d-flex flex-column gap-4 w-100\"].join(\" \")}>\n <div className=\"d-flex align-items-center justify-content-between gap-3 w-100\">\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n <i className=\"bi bi-gear-wide-connected\" />\n <span>Kanban board configuration</span>\n </h5>\n </div>\n {Object.keys(formState.metadata ?? {}).length > 0 && formElement}\n </div>\n )}\n {!Object.keys(formState.metadata ?? {}).length && (\n <div\n className=\"d-flex flex-column align-items-center justify-content-center gap-4\"\n style={{ height: 384 }}\n >\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n This community doesn't have a kanban board\n </h5>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n icon: { type: \"bootstrap_icon\", variant: \"bi-kanban-fill\" },\n isHidden: !permissions.can_configure,\n label: \"Create kanban board\",\n onClick: newViewInit,\n }}\n />\n </div>\n )}\n </div>\n );\n};\n\nreturn KanbanViewConfigurator(props);\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.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"\n }\n props={{ text: content }}\n />\n </Content>\n </Container>\n );\n}\n" }, "devhub.page.about": { "": "const PageHeader = styled.h2`\n color: #555555;\n font-size: 24px;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.24px;\n\n width: 100%;\n padding: 1rem 3.125rem;\n margin-left: auto;\n //background: #fff;\n\n margin-top: 1.5rem;\n margin-bottom: 1.5rem;\n\n @media screen and (max-width: 786px) {\n padding: 1rem;\n }\n`;\n\nconst Section = styled.div`\n padding: 1.5rem 8rem;\n display: flex;\n flex-direction: column;\n\n display: flex;\n flex-direction: column;\n\n gap: 2rem;\n\n @media screen and (max-width: 786px) {\n padding: 1rem;\n }\n\n h2 {\n color: #151515;\n font-size: 2rem;\n font-style: normal;\n font-weight: 700;\n line-height: 2rem; /* 55.556% */\n\n margin: 1rem;\n margin-left: 0;\n }\n\n p {\n color: #151515;\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 400;\n line-height: 150%;\n margin-bottom: 1rem;\n }\n\n h3 {\n color: #151515;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%;\n\n margin-top: 1rem;\n }\n`;\n\nreturn (\n <>\n <Widget src={`devhub.megha19.near/widget/devhub.components.island.banner`} />\n <Section>\n <h2>\n What is <span style={{ color: \"#00EC97\" }}>near</span>/dev/hub?\n </h2>\n\n <div>\n <p>\n DevHub brings together individuals, projects, and organizations to\n build a decentralized NEAR developer ecosystem. We provide social\n structure and tools to fuel innovation, collaboration, and community\n within the NEAR ecosystem. Whether you’re a blockchain developer or\n advocate for the open web, DevHub is your gateway to making a\n meaningful impact on NEAR and beyond.\n </p>\n\n <h3>Mission</h3>\n <p>\n Build a self-sufficient developer community to enable a thriving NEAR\n ecosystem.\n </p>\n\n <h3>Values</h3>\n <p>\n <ul>\n <li>\n <b>Decentralized</b>: We are building together.\n </li>\n <li>\n <b>Transparent</b>: All decision making and communication is open.\n </li>\n <li>\n <b>Self-sufficient</b>: We do not critically depend on any single\n entity.\n </li>\n <li>\n <b>Robust</b>: Any role or team can be duplicated or replaced.\n </li>\n <li>\n <b>High-velocity</b>: We need to move faster than classic OSS.\n </li>\n </ul>\n </p>\n\n <h3>Scope</h3>\n <p>\n DevHub covers a wide range of areas to support the NEAR developer\n ecosystem, including:\n <ul>\n <li>\n <b>Developer Tooling</b>: Contributing code to the NEAR Platform\n (blockchain and devtools) and facilitating technical decisions\n with recognized experts based on the needs of the community and\n ecosystem.\n </li>\n <li>\n <b>Developer Relations</b>: Engaging with the community across\n various social channels, gathering feedback, and improving\n documentation.\n </li>\n <li>\n <b>Deep-Tech Awareness</b>: Working with marketing partners to\n create awareness on interesting projects and technology areas.\n </li>\n <li>\n <b>Events and Hackathons</b>: Organizing events and empowering\n community leaders with resources to grow their local communities.\n </li>\n <li>\n <b>Education</b>: Partnering with universities across the globe to\n support students and scholars in exploring Web3 technologies\n </li>\n <li>\n <b>Platform</b>: Developing DevHub platform as a product to enable\n communities to collaborate and support each other.\n </li>\n </ul>\n </p>\n\n <h3>Our Contributors</h3>\n <p>\n DevHub operates through DevDAO, which provides social structures to\n support developers. Within DevDAO, we have a dedicated core team of\n moderators and community contributors who work across the key areas\n above. We welcome contributions from any community members who want to\n join us in building our ecosystem and their own skills!\n </p>\n\n <h3>Our Platform</h3>\n <p>\n Our main tool for interfacing is the DevHub Platform, where you can\n connect with others, share ideas and solutions, and access resources\n and support. You can also find communities working on a variety of\n areas, from protocol development to tooling and documentation.\n </p>\n\n <h3>Join Us in Shaping NEAR’s Future</h3>\n <p>\n Regardless of your background or where you are on your developer\n journey, we’re happy you’re here! We hope you’ll explore, find your\n people, and discover paths to contribute that are most gratifying for\n you.\n <br />\n Let’s build the open web together.\n </p>\n </div>\n </Section>\n </>\n);\n" }, "devhub.entity.addon.telegram.Configurator": { "": "const { Tile } =\n VM.require(\"devhub.megha19.near/widget/devhub.components.molecule.Tile\") ||\n (() => <></>);\n\nif (!Tile) {\n return <div>Loading...</div>;\n}\n\nconst { data, onSubmit } = props;\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n`;\n\nconst Item = styled.div`\n padding: 10px;\n margin: 5px;\n display: flex;\n align-items: center;\n flex-direction: row;\n gap: 10px;\n`;\n\nconst EditableField = styled.input`\n flex: 1;\n`;\nconst initialData = data.handles;\nconst [handles, setHandles] = useState(initialData || []);\nconst [newItem, setNewItem] = useState(\"\");\n\nconst handleAddItem = () => {\n if (newItem) {\n setHandles([...handles, newItem]);\n setNewItem(\"\");\n }\n};\n\nconst handleDeleteItem = (index) => {\n const updatedData = [...handles];\n updatedData.splice(index, 1);\n setHandles(updatedData);\n};\n\nconst handleSubmit = () => {\n onSubmit({ handles: handles.map((handle) => handle.trim()) });\n};\n\nreturn (\n <Tile className=\"p-3\">\n <Container>\n {handles.map((item, index) => (\n <Item key={index}>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: item,\n placeholder: \"Telegram Handle\",\n inputProps: {\n prefix: \"https://t.me/\",\n disabled: true,\n },\n }}\n />\n </div>\n <button\n className=\"btn btn-outline-danger\"\n onClick={() => handleDeleteItem(index)}\n >\n <i className=\"bi bi-trash-fill\" />\n </button>\n </Item>\n ))}\n <Item>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n onChange: (e) => setNewItem(e.target.value),\n value: newItem,\n placeholder: \"Telegram Handle\",\n inputProps: {\n prefix: \"https://t.me/\",\n },\n }}\n />\n </div>\n <button\n className=\"btn btn-success\"\n onClick={handleAddItem}\n disabled={newItem === \"\"}\n >\n <i className=\"bi bi-plus\" />\n </button>\n </Item>\n <div\n className={\"d-flex align-items-center justify-content-end gap-3 mt-4\"}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-success\" },\n disabled: initialData === handles,\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Submit\",\n onClick: handleSubmit,\n }}\n />\n </div>\n </Container>\n </Tile>\n);\n" }, "devhub.entity.addon.kanban.post_ticket": { "": "const columnId = props.columnId;\n\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\nhref || (href = () => {});\n\nconst AttractableDiv = styled.div`\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\nconst iconsByPostType = {\n Idea: \"bi-lightbulb\",\n Comment: \"bi-chat\",\n Solution: \"bi-rocket\",\n Attestation: \"bi-check-circle\",\n Sponsorship: \"bi-cash-coin\",\n};\n\nfunction getToken(token) {\n let amountUnit = \"\";\n if (typeof token === \"string\") {\n amountUnit = token;\n } else if (typeof token === \"object\") {\n const address = Object.values(token)?.[0]?.address ?? \"\";\n const ftMetadata = Near.view(address, \"ft_metadata\", {});\n if (ftMetadata !== null) {\n amountUnit = ftMetadata?.symbol;\n }\n }\n return amountUnit;\n}\nconst KanbanPostTicket = ({ metadata, data }) => {\n if (!data) return <div>Loading ...</div>;\n\n const {\n post_type,\n name,\n description,\n labels: tags,\n requested_sponsorship_amount,\n requested_sponsorship_token,\n requested_sponsor,\n amount,\n sponsorship_token,\n supervisor,\n } = data.snapshot;\n\n const features = {\n ...metadata.features,\n approved_sponsorship_value:\n post_type === \"Sponsorship\" &&\n metadata.features.approved_sponsorship_value,\n sponsorship_supervisor:\n post_type === \"Sponsorship\" && metadata.features.sponsorship_supervisor,\n };\n\n const footer = (\n <div className=\"card-header d-flex justify-content-between gap-3 align-items-center\">\n {features.like_count && (\n <div className=\"d-flex gap-2 align-items-center\">\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.atom.Icon`}\n props={{\n type: \"bootstrap_icon\",\n variant: \"bi-heart-fill\",\n }}\n />\n {data.likes.length}\n </div>\n )}\n <div className=\"d-flex justify-content-end w-100\">\n <a\n className=\"card-link\"\n href={href(\"Post\", { id: data.id })}\n role=\"button\"\n target=\"_blank\"\n title=\"Open in new tab\"\n >\n <i className=\"bi bi-share\" />\n </a>\n </div>\n </div>\n );\n\n const titleArea = (\n <span>\n {features.type ? (\n <i className={`bi ${iconsByPostType[post_type]}`} />\n ) : null}\n <span>{name}</span>\n </span>\n );\n\n const sponsorshipValue = (\n <span className=\"d-flex flex-nowrap gap-1\">\n <span>{requested_sponsorship_amount ?? amount}</span>\n <span>{requested_sponsorship_token ?? getToken(sponsorship_token)}</span>\n </span>\n );\n\n const requestedSponsor = (\n <Widget\n className=\"flex-wrap\"\n src={`neardevgov.near/widget/ProfileLine`}\n props={{\n accountId: requested_sponsor ?? supervisor,\n hideAccountId: true,\n tooltip: true,\n }}\n />\n );\n\n const descriptionArea =\n post_type === \"Comment\" ? (\n <div\n style={{ maxHeight: \"6em\", wordBreak: \"break-all\", overflow: \"hidden\" }}\n >\n <Markdown className=\"card-text\" text={description} />\n </div>\n ) : null;\n\n const tagList =\n Array.isArray(tags) && features.tags ? (\n <div className=\"d-flex flex-wrap gap-2 m-0\">\n {(tags ?? []).map((tag) => (\n <a href={href(\"Feed\", { tag })} key={tag}>\n <span className=\"badge text-bg-primary me-1\">{tag}</span>\n </a>\n ))}\n </div>\n ) : null;\n\n const showFunding = features.approved_sponsorship_value;\n const showSponsor = features.sponsorship_supervisor;\n\n return (\n <AttractableDiv className=\"card\">\n <div\n className=\"card-body d-flex flex-column gap-2\"\n style={{ fontSize: 14 }}\n >\n <div className=\"d-flex justify-content-between gap-2\">\n <span className=\"card-text gap-2\">{titleArea}</span>\n {features.author && (\n <a\n href={`https://near.org/mob.near/widget/ProfilePage?accountId=${data.author_id}`}\n s\n style={{ minWidth: 20 }}\n >\n <Widget\n src=\"mob.near/widget/ProfileImage\"\n props={{\n metadata,\n accountId: data.author_id,\n widgetName,\n style: { height: \"1.8em\", width: \"1.8em\", minWidth: \"1.8em\" },\n }}\n />\n </a>\n )}\n </div>\n {descriptionArea}\n {showFunding && (\n <span className=\"d-flex flex-wrap gap-2\">\n <span>Amount:</span>\n {sponsorshipValue}\n </span>\n )}\n {showSponsor && (\n <div className=\"d-flex flex-wrap gap-2\">\n <span>{`${\n post_type === \"Solution\" ? \"Sponsor\" : \"Supervisor\"\n }:`}</span>{\" \"}\n {requestedSponsor}{\" \"}\n </div>\n )}\n {tagList}\n </div>\n {footer}\n </AttractableDiv>\n );\n};\n\nreturn KanbanPostTicket(props);\n" }, "core.lib.struct": { "": "/**\n * Deeply updates an object's field based on the given path and transformation function.\n *\n * @param {Object} target - The target object to update.\n * @param {Array} path - The path to the field to update.\n * @param {Function} transform - The transformation function to apply.\n * @returns {Object} - The updated object.\n */\nconst deepFieldUpdate = (target, path, transform) => {\n if (path.length === 0) {\n return transform(target);\n }\n\n const [nextNodeKey, ...remainingPath] = path;\n\n return {\n ...target,\n [nextNodeKey]: deepFieldUpdate(\n target[nextNodeKey] ?? {},\n remainingPath,\n transform\n ),\n };\n};\n\n/**\n * Checks if two inputs (objects or arrays) are deeply equal.\n *\n * @param {Object|Array} input1 - The first input.\n * @param {Object|Array} input2 - The second input.\n * @returns {boolean} - True if the inputs are deeply equal, false otherwise.\n */\nconst isEqual = (input1, input2) => {\n const input1Str = JSON.stringify(input1);\n const input2Str = JSON.stringify(input2);\n return input1Str === input2Str;\n};\n\n/**\n * Creates a new object with sorted keys based on the input object.\n *\n * @param {Object} input - The input object.\n * @returns {Object} - A new object with sorted keys.\n */\nconst toOrdered = (input) => {\n if (typeof input !== \"object\" || input === null) {\n return {};\n }\n\n return Object.keys(input)\n .sort()\n .reduce((output, key) => ({ ...output, [key]: input[key] }), {});\n};\n\n/**\n * Picks specified keys from an object and returns a new object with those keys.\n *\n * @param {Object} sourceObject - The source object to pick keys from.\n * @param {Array} keysToPick - An array of keys to pick.\n * @returns {Object} - A new object containing the selected keys.\n */\nconst pick = (sourceObject, keysToPick) => {\n if (typeof sourceObject !== \"object\" || sourceObject === null) {\n return {};\n }\n\n return Object.fromEntries(\n Object.entries(sourceObject).filter(([key, _]) => keysToPick.includes(key))\n );\n};\n\n/**\n * Checks if the input matches the expected type.\n *\n * @param {Object} input - The input to check.\n * @returns {boolean} - True if the input is an object and not an array or null, false otherwise.\n */\nconst typeMatch = (input) =>\n input !== null && typeof input === \"object\" && !Array.isArray(input);\n\nconst defaultFieldUpdate = ({\n input,\n lastKnownValue,\n params: { arrayDelimiter },\n}) => {\n switch (typeof input) {\n case \"boolean\":\n return input;\n\n case \"object\": {\n if (Array.isArray(input) && typeof lastKnownValue === \"string\") {\n return input.join(arrayDelimiter ?? \",\");\n } else {\n return Array.isArray(lastKnownValue)\n ? [...lastKnownValue, ...input]\n : { ...lastKnownValue, ...input };\n }\n }\n\n case \"string\":\n return Array.isArray(lastKnownValue)\n ? input.split(arrayDelimiter ?? \",\").map((string) => string.trim())\n : input;\n\n default: {\n if ((input ?? null) === null) {\n switch (typeof lastKnownValue) {\n case \"boolean\":\n return !lastKnownValue;\n\n default:\n return lastKnownValue;\n }\n } else return input;\n }\n }\n};\n\nreturn {\n deepFieldUpdate,\n isEqual,\n pick,\n toOrdered,\n typeMatch,\n defaultFieldUpdate,\n};\n" }, "devhub.entity.community.Discussions": { "": "const NEW_DISCUSSION_POSTED_CONTENT_STORAGE_KEY =\n \"new_discussion_posted_content\";\nconst { handle } = props;\nconst { getCommunity, setCommunitySocialDB } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\ngetCommunity = getCommunity || (() => <></>);\nsetCommunitySocialDB = setCommunitySocialDB || (() => <></>);\n\nconst communityData = getCommunity({ handle });\n\nconst MainContent = styled.div`\n padding-left: 2rem;\n flex: 3;\n @media screen and (max-width: 960px) {\n padding-left: 0rem;\n }\n .post:hover {\n background-color: inherit !important;\n }\n`;\n\nconst SidebarContainer = styled.div`\n flex: 1;\n`;\n\nconst Heading = styled.div`\n font-size: 19px;\n font-weight: 600;\n`;\n\nconst SubHeading = styled.div`\n font-size: 15px;\n font-weight: 600;\n`;\n\nconst Container = styled.div`\n flex-wrap: no-wrap;\n max-width: 100%;\n\n .max-width-100 {\n max-width: 100%;\n }\n @media screen and (max-width: 960px) {\n flex-wrap: wrap;\n }\n\n .card {\n border-radius: 1rem !important;\n }\n\n .display-none {\n display: none;\n }\n`;\n\nconst Tag = styled.div`\n border-top-right-radius: 50px;\n border-bottom-right-radius: 50px;\n border-top-left-radius: 50px;\n border-bottom-left-radius: 50px;\n padding-inline: 0.8rem;\n padding-block: 0.3rem;\n display: flex;\n gap: 0.5rem;\n border-width: 1px;\n border-style: solid;\n font-size: 14px;\n color: rgba(0, 236, 151, 1);\n font-weight: 800;\n`;\n\nconst [sort, setSort] = useState(\"desc\");\nconst [isTransactionFinished, setIsTransactionFinished] = useState(false);\n\nconst discussionsAccountId =\n \"discussions.\" + handle + \".community.devhub.near\";\n\nfunction checkIfReposted(blockHeight) {\n Near.asyncView(\"social.near\", \"get\", {\n keys: [`${discussionsAccountId}/index/**`],\n })\n .then((response) => {\n const repost = response[discussionsAccountId].index.repost;\n\n if (repost && repost.indexOf(`\"blockHeight\":${blockHeight}`) > -1) {\n setIsTransactionFinished(true);\n } else {\n setTimeout(() => checkIfReposted(), 500);\n }\n })\n .catch((error) => {\n console.error(\n \"DevHub Error [Discussions]: checkIfReposted failed\",\n error\n );\n });\n}\n\nfunction repostOnDiscussions(blockHeight) {\n Near.call([\n {\n contractName: \"devhub.near\",\n methodName: \"create_discussion\",\n args: {\n handle,\n block_height: blockHeight,\n },\n gas: Big(10).pow(14),\n },\n ]);\n checkIfReposted(blockHeight);\n}\n\nasync function checkHashes() {\n if (props.transactionHashes) {\n asyncFetch(\"https://rpc.mainnet.near.org\", {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: \"dontcare\",\n method: \"tx\",\n params: [props.transactionHashes, context.accountId],\n }),\n }).then((transaction) => {\n if (transaction !== null) {\n const transaction_method_name =\n transaction?.body?.result?.transaction?.actions[0].FunctionCall\n .method_name;\n\n if (transaction_method_name === \"set\") {\n getBlockHeightAndRepost();\n }\n\n // show the latest created post to user\n if (transaction_method_name === \"create_discussion\") {\n console.log(\"Discussions created in the last call, show it to user.\");\n }\n }\n });\n }\n}\n\nfunction getBlockHeightAndRepost() {\n const newDiscussionPostedContent = Storage.get(\n NEW_DISCUSSION_POSTED_CONTENT_STORAGE_KEY\n );\n console.log(\"new discussion content\", newDiscussionPostedContent);\n\n Near.asyncView(\"social.near\", \"get\", {\n keys: [`${context.accountId}/post/**`],\n options: {\n with_block_height: true,\n },\n })\n .then((response) => {\n const post_main = response[context.accountId].post.main;\n const content = post_main[\"\"];\n if (content === newDiscussionPostedContent) {\n const blockHeight = post_main[\":block\"];\n console.log(\"content matches\", blockHeight, post_main);\n repostOnDiscussions(blockHeight);\n } else {\n console.log(\n \"content does not match (yet)\",\n post_main,\n newDiscussionPostedContent\n );\n setTimeout(() => getBlockHeightAndRepost(), 500);\n }\n })\n .catch((error) => {\n console.log(\n \"DevHub Error [Discussions]: getBlockHeightAndRepost failed\",\n error\n );\n });\n}\n\ncheckHashes();\n\nreturn (\n <div className=\"w-100\" style={{ maxWidth: \"100%\" }}>\n <Container className=\"d-flex gap-3 m-3 pl-2\">\n <MainContent className=\"max-width-100\">\n <div className=\"d-flex flex-column gap-4\">\n {context.accountId && (\n <div className=\"card p-4\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.community.Compose\"}\n props={{\n isFinished: () => isTransactionFinished,\n onSubmit: (v) => {\n console.log(\"ON SUBMIT\");\n Storage.set(\n NEW_DISCUSSION_POSTED_CONTENT_STORAGE_KEY,\n v.post.main\n );\n\n Social.set(v, {\n force: true,\n onCommit: () => {\n getBlockHeightAndRepost();\n },\n });\n },\n profileAccountId: context.accountId,\n }}\n />\n </div>\n )}\n <div className=\"d-flex flex-wrap justify-content-between\">\n <Heading>Discussions</Heading>\n <div className={\"d-flex align-items-center gap-2\"}>\n <select\n name=\"sort\"\n id=\"sort\"\n class=\"form-select\"\n value={sort}\n onChange={(e) => {\n setSort(e.target.value);\n }}\n >\n <option selected value=\"desc\">\n Latest\n </option>\n <option value=\"recentcommentdesc\">Last Commented</option>\n </select>\n </div>\n </div>\n <div className={\"card p-4\"}>\n <Widget\n key=\"feed\"\n src=\"devhub.megha19.near/widget/devhub.components.feed.SubscribedFeed\"\n props={{\n sort: sort,\n accounts: [\n `discussions.${handle}.community.devhub.near`,\n ],\n }}\n />\n </div>\n </div>\n </MainContent>\n <SidebarContainer>\n <div className=\"d-flex flex-column gap-3\">\n <div className=\"card p-4\">\n <div className=\"mb-2\">{communityData?.description}</div>\n <div className=\"d-flex gap-2 flex-wrap\">\n <Tag>{communityData?.tag} </Tag>\n </div>\n </div>\n <div className=\"card p-4 d-flex flex-column gap-2\">\n <SubHeading>Community Admins</SubHeading>\n {(communityData?.admins ?? []).map((accountId) => (\n <div\n key={accountId}\n className=\"d-flex\"\n style={{ fontWeight: 500 }}\n >\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.ProfileCard\"\n props={{ accountId }}\n />\n </div>\n ))}\n </div>\n </div>\n </SidebarContainer>\n </Container>\n </div>\n);\n" }, "devhub.entity.proposal.Profile": { "": "const accountId = props.accountId;\nconst size = props.size ?? \"md\";\nconst showAccountId = props.showAccountId;\nconst Avatar = styled.div`\n &.sm {\n min-width: 30px;\n max-width: 30px;\n min-height: 30px;\n max-height: 30px;\n }\n &.md {\n min-width: 40px;\n max-width: 40px;\n min-height: 40px;\n max-height: 40px;\n }\n pointer-events: none;\n flex-shrink: 0;\n border: 1px solid #eceef0;\n overflow: hidden;\n border-radius: 40px;\n transition: border-color 200ms;\n\n img {\n object-fit: cover;\n width: 100%;\n height: 100%;\n margin: 0 !important;\n }\n`;\nconst profile = Social.get(`${accountId}/profile/**`, \"final\");\nconst profileUrl = `/near/widget/ProfilePage?accountId=${accountId}`;\nreturn (\n <Link href={profileUrl}>\n <div className=\"d-flex gap-2 align-items-center\">\n <Avatar className={size}>\n <Widget\n src=\"mob.near/widget/Image\"\n props={{\n image: profile.image,\n alt: profile.name,\n fallbackUrl:\n \"https://ipfs.near.social/ipfs/bafkreibiyqabm3kl24gcb2oegb7pmwdi6wwrpui62iwb44l7uomnn3lhbi\",\n }}\n />\n </Avatar>\n {showAccountId && (\n <div>\n {(accountId ?? \"\").substring(0, 20)}\n {(accountId ?? \"\").length > 20 ? \"...\" : \"\"}\n </div>\n )}\n </div>\n </Link>\n);\n" }, "devhub.page.feed": { "": "const { author, recency, tag } = props;\n\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nif (!href) {\n return <p>Loading modules...</p>;\n}\n\nconst Gradient = styled.div`\n height: 250px;\n text-align: center;\n background: radial-gradient(\n circle,\n rgba(29, 55, 57, 1) 30%,\n rgba(24, 24, 24, 1) 80%\n );\n\n font-family: Arial, sans-serif;\n\n .text-primary-gradient {\n color: #53fdca;\n -webkit-text-fill-color: transparent;\n background-image: linear-gradient(#8e76ba, #1ed2f0);\n -webkit-background-clip: text;\n background-clip: text;\n }\n\n .subtitle-above {\n font-size: 18px;\n letter-spacing: 1px;\n font-family: Courier, monospace;\n }\n\n .subtitle-below {\n font-size: 16px;\n }\n\n .slogan {\n font-weight: 600;\n font-size: 60px;\n }\n`;\n\nconst FeedPage = ({ recency, tag }) => {\n return (\n <div className=\"w-100\">\n <Widget src={`devhub.megha19.near/widget/devhub.components.island.banner`} />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.feature.post-search.panel\"}\n props={{\n hideHeader: false,\n children: (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.components.molecule.PostControls\"\n }\n props={{\n title: \"Post\",\n href: href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"create\" },\n }),\n }}\n />\n ),\n recency,\n tag,\n author,\n transactionHashes: props.transactionHashes,\n }}\n />\n </div>\n );\n};\n\nreturn FeedPage(props);\n" }, "devhub.entity.proposal.ConfirmReviewModal": { "": "const isOpen = props.isOpen;\nconst onCancelClick = props.onCancelClick;\nconst onReviewClick = props.onReviewClick;\n\nconst Modal = styled.div`\n display: ${({ hidden }) => (hidden ? \"none\" : \"flex\")};\n position: fixed;\n inset: 0;\n justify-content: center;\n align-items: center;\n opacity: 1;\n z-index: 999;\n .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n\n @media screen and (max-width: 768px) {\n h5 {\n font-size: 16px !important;\n }\n }\n\n .btn {\n font-size: 14px;\n }\n`;\n\nconst ModalBackdrop = styled.div`\n position: absolute;\n inset: 0;\n background-color: rgba(0, 0, 0, 0.5);\n opacity: 0.4;\n`;\n\nconst ModalDialog = styled.div`\n padding: 2em;\n z-index: 999;\n overflow-y: auto;\n max-height: 85%;\n margin-top: 5%;\n width: 50%;\n\n @media screen and (max-width: 768px) {\n margin: 2rem;\n width: 100%;\n }\n`;\n\nconst ModalHeader = styled.div`\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding-bottom: 4px;\n`;\n\nconst ModalFooter = styled.div`\n padding-top: 4px;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: items-center;\n`;\n\nconst CloseButton = styled.button`\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: white;\n padding: 0.5em;\n border-radius: 6px;\n border: 0;\n color: #344054;\n\n &:hover {\n background-color: #d3d3d3;\n }\n`;\n\nconst ConfirmButton = styled.button`\n padding: 0.7em;\n border-radius: 6px;\n border: 0;\n box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);\n background-color: #12b76a;\n color: white;\n\n &:hover {\n background-color: #0e9f5d;\n }\n`;\n\nconst ModalContent = styled.div`\n flex: 1;\n font-size: 14px;\n margin-top: 4px;\n margin-bottom: 4px;\n overflow-y: auto;\n max-height: 50%;\n\n @media screen and (max-width: 768px) {\n font-size: 12px !important;\n }\n`;\n\nconst NoButton = styled.button`\n background: transparent;\n border: none;\n padding: 0;\n margin: 0;\n box-shadow: none;\n`;\n\nreturn (\n <>\n <Modal hidden={!isOpen}>\n <ModalBackdrop />\n <ModalDialog className=\"card\">\n <ModalHeader>\n <h5 className=\"mb-0\">Confirm proposal is ready for for review</h5>\n </ModalHeader>\n <ModalContent>\n Once in official review, you can no longer edit the proposal. But\n comments are still open. <br />\n Are you sure you are ready to proceed?\n </ModalContent>\n <div className=\"d-flex gap-2 align-items-center justify-content-end mt-2\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-secondary\" },\n label: \"Cancel\",\n onClick: onCancelClick,\n }}\n />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"black-btn\" },\n label: \"Ready for review\",\n onClick: onReviewClick,\n }}\n />\n </div>\n </ModalDialog>\n </Modal>\n </>\n);\n" }, "devhub.entity.addon.blog.editor.content": { "": "const { Card } =\n VM.require(\"devhub.megha19.near/widget/devhub.entity.addon.blog.Card\") ||\n (() => <></>);\nconst { Page } =\n VM.require(\"devhub.megha19.near/widget/devhub.entity.addon.blog.Page\") ||\n (() => <></>);\n\nconst categories = [\n {\n label: \"Guide\",\n value: \"guide\",\n },\n {\n label: \"News\",\n value: \"news\",\n },\n {\n label: \"Reference\",\n value: \"reference\",\n },\n];\n\nconst selectOptions = useMemo(\n () =>\n categories.map((it) => ({\n label: it.label,\n value: it.value,\n })),\n [categories]\n);\n\nconst Banner = styled.div`\n border-radius: var(--bs-border-radius-xl) !important;\n height: 100%;\n\n & > div :not(.btn) {\n position: absolute;\n display: none;\n margin: 0 !important;\n width: 0 !important;\n height: 0 !important;\n }\n\n .btn {\n padding: 0.5rem 0.75rem !important;\n min-height: 32;\n line-height: 1;\n\n border: none;\n border-radius: 50px;\n --bs-btn-color: #ffffff;\n --bs-btn-bg: #087990;\n --bs-btn-border-color: #087990;\n --bs-btn-hover-color: #ffffff;\n --bs-btn-hover-bg: #055160;\n --bs-btn-hover-border-color: #055160;\n --bs-btn-focus-shadow-rgb: 49, 132, 253;\n --bs-btn-active-color: #ffffff;\n --bs-btn-active-bg: #055160;\n --bs-btn-active-border-color: #055160;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n opacity: 0.8;\n\n &:hover {\n opacity: 1;\n }\n }\n`;\n\nconst { data, handle, onSubmit } = props;\n\nconst initialData = data;\n\nconst [content, setContent] = useState(initialData.content || \"\");\nconst [title, setTitle] = useState(initialData.title || \"\");\nconst [subtitle, setSubtitle] = useState(initialData.subtitle || \"\");\nconst [description, setDescription] = useState(initialData.description || \"\");\nconst [author, setAuthor] = useState(initialData.author || \"\");\nconst [previewMode, setPreviewMode] = useState(\"card\"); // \"card\" or \"page\"\nconst [date, setDate] = useState(initialData.date || new Date());\nconst [category, setCategory] = useState(\"guide\");\n\nconst Container = styled.div`\n width: 100%;\n margin: 0 auto;\n padding: 20px;\n text-align: left;\n`;\n\nconst hasDataChanged = () => {\n return (\n content !== initialData.content ||\n title !== initialData.title ||\n author !== initialData.author ||\n subtitle !== initialData.subtitle ||\n description !== initialData.description ||\n date !== initialData.date ||\n category !== initialData.category\n );\n};\n\nconst handlePublish = () => {\n onSubmit &&\n onSubmit(\n {\n id: data.id || undefined,\n title,\n subtitle,\n description,\n date,\n content,\n author,\n category,\n community: handle,\n },\n data.id !== undefined\n );\n};\n\nfunction Preview() {\n switch (previewMode) {\n case \"card\": {\n return (\n <Card\n data={{\n title,\n subtitle,\n description,\n date,\n content,\n author,\n category,\n community: handle,\n }}\n />\n );\n }\n case \"page\": {\n return (\n <Page\n data={{\n title,\n subtitle,\n description,\n date,\n content,\n author,\n category,\n community: handle,\n }}\n />\n );\n }\n }\n}\n\nreturn (\n <Container>\n <ul className=\"nav nav-tabs\" id=\"editPreviewTabs\" role=\"tablist\">\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className=\"nav-link active\"\n id=\"edit-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#edit\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"edit\"\n aria-selected=\"true\"\n >\n Edit\n </button>\n </li>\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className=\"nav-link\"\n id=\"preview-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#preview\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"preview\"\n aria-selected=\"false\"\n >\n Preview\n </button>\n </li>\n </ul>\n <div className=\"tab-content\" id=\"editPreviewTabsContent\">\n <div\n className=\"tab-pane show active p-4\"\n id=\"edit\"\n role=\"tabpanel\"\n aria-labelledby=\"edit-tab\"\n >\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.addon.blog.editor.form\"\n props={{\n title,\n setTitle,\n subtitle,\n setSubtitle,\n options: selectOptions,\n category,\n setCategory,\n description,\n setDescription,\n author,\n setAuthor,\n date,\n setDate,\n content,\n setContent,\n }}\n />\n <div\n className={\"d-flex align-items-center justify-content-end gap-3 mt-4\"}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-success\" },\n disabled: !hasDataChanged(),\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Publish\",\n onClick: handlePublish,\n }}\n />\n </div>\n </div>\n <div\n className=\"tab-pane\"\n id=\"preview\"\n role=\"tabpanel\"\n aria-labelledby=\"preview-tab\"\n style={{ position: \"relative\" }}\n >\n <div style={{ position: \"absolute\", top: 10, right: 0, zIndex: 9999 }}>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Switch\"\n props={{\n currentValue: previewMode,\n key: \"previewMode\",\n onChange: (e) => setPreviewMode(e.target.value),\n options: [\n { label: \"Card\", value: \"card\" },\n { label: \"Page\", value: \"page\" },\n ],\n\n title: \"Preview mode selection\",\n }}\n />\n </div>\n <div className=\"w-100 h-100 p-4\">\n <Preview />\n </div>\n </div>\n </div>\n </Container>\n);\n" }, "devhub.components.island.support": { "": "const Items = [\n {\n heading: (\n <>\n Developer\n <br />\n Resources\n </>\n ),\n description: \"Learn the fundamentals of NEAR and build with confidence\",\n cta: {\n href: \"https://docs.near.org\",\n title: \"Read ↗\",\n },\n },\n {\n heading: <>Office Hours</>,\n description: (\n <>\n Need some help?\n <br /> DevRel contributors are available to answer your technical\n questions\n </>\n ),\n cta: {\n href: \"/devhub.near/widget/app?page=community&handle=devrel&tab=office-hours-support\",\n title: \"Join ↗\",\n },\n },\n {\n heading: <>Get Funding</>,\n description:\n \"Explore funding opportunities from DevHub to fuel your vision\",\n cta: {\n href: \"/devhub.near/widget/app?page=community&handle=developer-dao&tab=funding\",\n title: \"Learn more ↗\",\n },\n },\n];\n\nconst Circle = styled.div`\n display: flex;\n width: 18.75rem;\n height: 18.75rem;\n padding: 2.25rem;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n gap: 1rem;\n flex-shrink: 0;\n\n border-radius: 22.5rem;\n border: 1px solid #00ec97;\n background: #f4f4f4;\n\n h3 {\n color: #101820;\n text-align: center;\n font-size: 1.75rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 36px */\n }\n\n p {\n color: #101820;\n text-align: center;\n font-size: 1.125rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.72px;\n }\n\n a {\n color: #555555;\n font-size: 1.125rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n }\n`;\n\nconst Container = styled.div`\n padding: 3rem;\n padding-top: 0;\n margin-top: 1.5rem;\n\n @media screen and (max-width: 786px) {\n padding: 1.5rem;\n padding-top: 0;\n }\n`;\n\nconst ItemsContainer = styled.div`\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n width: 100%;\n\n flex-wrap: wrap;\n gap: 3rem;\n\n @media screen and (max-width: 768px) {\n flex-direction: column;\n gap: 1rem;\n }\n`;\n\nconst Content = (\n <Container>\n <ItemsContainer>\n {Items.map((it) => (\n <Circle key={Math.random()}>\n <h3>{it.heading}</h3>\n <p>{it.description}</p>\n <a href={it.cta.href}>{it.cta.title}</a>\n </Circle>\n ))}\n </ItemsContainer>\n </Container>\n);\n\nreturn (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.island.home-section\"\n props={{\n title: \"/get support\",\n children: Content,\n background: true,\n }}\n />\n);\n" }, "devhub.components.molecule.ListEditor": { "": "const { data, setList, validate, invalidate } = props;\n\nconst [newItem, setNewItem] = useState(\"\");\n\nconst handleAddItem = () => {\n if (validate(newItem)) {\n setList([...data.list, newItem]);\n setNewItem(\"\");\n } else {\n return invalidate();\n }\n};\n\nconst handleDeleteItem = (index) => {\n const updatedData = [...data.list];\n updatedData.splice(index, 1);\n setList(updatedData);\n};\n\nconst Item = styled.div`\n padding: 10px;\n margin: 5px;\n display: flex;\n align-items: center;\n flex-direction: row;\n gap: 10px;\n`;\n\nreturn (\n <>\n {data.list.map((item, index) => (\n <Item key={index}>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: item,\n skipPaddingGap: true,\n placeholder: data.placeholder,\n inputProps: {\n prefix: data.prefix,\n disabled: true,\n },\n }}\n />\n </div>\n <button\n className=\"btn btn-outline-danger\"\n onClick={() => handleDeleteItem(index)}\n >\n <i className=\"bi bi-trash-fill\" />\n </button>\n </Item>\n ))}\n {data.list.length < data.maxLength && (\n <Item>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n skipPaddingGap: true,\n onChange: (e) => setNewItem(e.target.value),\n value: newItem,\n placeholder: data.placeholder,\n inputProps: {\n prefix: data.prefix,\n },\n key: data.prefix + \"new-list-item\",\n }}\n />\n </div>\n <button\n className=\"btn btn-success add-member\"\n onClick={handleAddItem}\n disabled={newItem === \"\"}\n data-testid=\"add-to-list\"\n >\n <i className=\"bi bi-plus\" />\n </button>\n </Item>\n )}\n </>\n);\n" }, "devhub.notification.Item": { "": "const { value } = props;\n\nreturn (\n <Widget\n src=\"devhub.near/widget/devhub.notification.LR\"\n props={{\n L: (\n <Widget\n src=\"devhub.near/widget/devhub.notification.Left\"\n props={{ type: value.type, proposal: value.proposal }}\n />\n ),\n R: (\n <Widget\n src=\"devhub.near/widget/DevGov.notification.Right\"\n props={{ proposal: value.proposal }}\n />\n ),\n ...props,\n }}\n />\n);\n" }, "devhub.components.atom.Toggle": { "": "const ToggleRoot = styled.div`\n justify-content: space-between;\n width: fit-content;\n max-width: 100%;\n`;\n\nconst ToggleSwitchRoot = styled(\"Switch.Root\")`\n all: unset;\n display: block;\n width: 42px;\n height: 25px;\n background-color: #d1d1d1;\n border-radius: 9999px;\n position: relative;\n box-shadow: 0 2px 10px var(--blackA7);\n\n &[data-state=\"checked\"] {\n background-color: #00d084;\n }\n\n &[data-disabled=\"\"] {\n opacity: 0.7;\n }\n`;\n\nconst ToggleSwitchThumb = styled(\"Switch.Thumb\")`\n all: unset;\n display: block;\n width: 21px;\n height: 21px;\n border-radius: 9999px;\n transition: transform 100ms;\n transform: translateX(2px);\n will-change: transform;\n\n &[data-state=\"checked\"] {\n transform: translateX(19px);\n }\n`;\n\nconst ToggleLabel = styled.label`\n white-space: nowrap;\n`;\n\nconst Toggle = ({\n className,\n direction,\n disabled,\n inputProps,\n key,\n label,\n onChange,\n value: checked,\n ...rest\n}) => (\n <ToggleRoot\n className={[\n \"d-flex justify-content-between, align-items-center gap-3 p-2\",\n direction === \"rtl\" ? \"flex-row-reverse\" : \"\",\n className,\n ].join(\" \")}\n {...rest}\n >\n <ToggleLabel htmlFor={`toggle-${key}`}>{label}</ToggleLabel>\n\n <ToggleSwitchRoot\n className=\"shadow-none\"\n id={`toggle-${key}`}\n onCheckedChange={disabled ? null : onChange}\n {...{ checked, disabled, ...inputProps }}\n >\n {!disabled && <ToggleSwitchThumb className=\"bg-light shadow\" />}\n </ToggleSwitchRoot>\n </ToggleRoot>\n);\n\nreturn Toggle(props);\n" }, "devhub.entity.proposal.LoginScreen": { "": "const Container = styled.div`\n height: 60vh;\n .card-custom {\n box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);\n border-radius: 5px;\n background-color: white;\n }\n\n @media screen and (max-width: 768px) {\n .card-custom {\n margin: 2rem;\n }\n }\n\n .bg-grey {\n background-color: #f3f3f2;\n }\n\n .cursor {\n cursor: pointer;\n }\n\n .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n`;\n\nreturn (\n <Container className=\"d-flex justify-content-center align-items-center w-100\">\n <div class=\"card-custom\">\n <div class=\"card-body p-4 d-flex flex-column gap-2 justify-content-center align-items-center\">\n <i class=\"bi bi-person-circle h2\"></i>\n <div className=\"h5\">Not Logged in</div>\n <p className=\"text-muted text-center\">\n You must log in to create or interact with proposals.\n </p>\n <div className=\"d-flex gap-2 align-items-center justify-content-end mt-2\">\n <Link to=\"https://near.org/signin\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-secondary\" },\n label: \"Sign In\",\n }}\n />\n </Link>\n <Link to=\"https://near.org/signup\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"black-btn\" },\n label: \"Create Account\",\n }}\n />\n </Link>\n </div>\n </div>\n </div>\n </Container>\n);\n" }, "devhub.components.island.explore": { "": "const Card = styled.div`\n display: flex;\n max-width: 20rem;\n max-height: 17.5rem;\n padding: 1.5rem;\n flex-direction: column;\n justify-content: space-between;\n align-items: center;\n\n background: #fff;\n border-radius: 1rem;\n\n h3 {\n color: #555555; //#00ec97;\n text-align: center;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n }\n\n p {\n color: #555;\n text-align: center;\n font-size: 1rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 24px */\n }\n\n a {\n color: #555555; //#00ec97;\n text-align: center;\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 24px */\n }\n\n @media screen and (max-width: 768px) {\n h3 {\n font-size: 1.5rem;\n }\n\n p,\n a {\n font-size: 1rem;\n }\n\n padding: 1rem;\n }\n`;\n\nconst SectionCard = ({ title, description, href }) => {\n return (\n <Card>\n <h3>{title}</h3>\n <p>{description}</p>\n <a href={href}>Learn more →</a>\n </Card>\n );\n};\n\nconst Cards = [\n {\n title: \"217 Validators\",\n description:\n \"to ensure security, liveness, and fault tolerance of the network\",\n href: \"https://nearscope.net/\",\n },\n {\n title: \"<1s, <1¢\",\n description:\n \"Fast and cost-efficient transactions. 1s to update values with minimal fees\",\n href: \"https://nearblocks.io/\",\n },\n {\n title: \"Awesome DevEx\",\n description:\n \"NEAR lets developers innovate with familiar tools: TypeScript, Rust, Solidity\",\n href: \"https://docs.near.org/\",\n },\n {\n title: \"Horizontal Scaling\",\n description:\n \"Nightshade ensures maximum performance thanks to its sharded design\",\n href: \"https://docs.near.org/concepts/advanced/papers\",\n },\n];\n\nconst CTA = styled.a`\n display: flex;\n padding: 0.875rem 1rem;\n align-items: center;\n gap: 0.5rem;\n\n border-radius: 1rem;\n background: #00ec97;\n\n color: #f4f4f4 !important;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.03rem;\n\n width: max-content;\n text-decoration: none;\n\n &:hover {\n text-decoration: none;\n background: #555555;\n }\n\n @media screen and (max-width: 768px) {\n color: #f4f4f4 !important;\n font-size: 1.125rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 24px */\n letter-spacing: -0.4px;\n\n margin-left: auto;\n margin-right: auto;\n\n display: flex;\n padding: 14px 16px;\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n gap: 8px;\n\n border-radius: 16px;\n background: #00ec97;\n }\n`;\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n gap: 3rem;\n\n padding: 3rem;\n padding-top: 0;\n\n @media screen and (max-width: 768px) {\n padding: 1.5rem;\n padding-top: 0;\n }\n`;\n\nconst CardsContainer = styled.div`\n display: flex;\n justify-content: space-between;\n gap: 1.5rem;\n\n @media screen and (max-width: 768px) {\n display: grid;\n grid-template-columns: 1fr 1fr;\n /* gap: 2.25rem; */\n }\n`;\n\nconst Content = (\n <Container>\n <CardsContainer>\n {Cards.map((card) => (\n <SectionCard\n title={card.title}\n description={card.description}\n href={card.href}\n key={Math.random()}\n />\n ))}\n </CardsContainer>\n <CTA href=\"https://near.org/ecosystem\" target=\"_blank\">\n Explore the Open Web on NEAR →\n </CTA>\n </Container>\n);\n\nreturn (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.island.home-section\"\n props={{\n title: \"/explore\",\n titleColor: \"#555555\",\n description:\n \"NEAR Protocol is your fast, low-cost and reliable gateway to the Open Web\",\n children: Content,\n }}\n />\n);\n" } } } } }

Transaction Execution Plan

Convert Transaction To Receipt
Gas Burned:
1 Tgas
Tokens Burned:
0.00018 
Receipt:
Predecessor ID:
Receiver ID:
Gas Burned:
261 Tgas
Tokens Burned:
0.02617 
Called method: 'set' in contract: social.near
Arguments:
{ "data": { "devhub.megha19.near": { "widget": { "devhub.components.molecule.CommunityControl": { "": "const { className, title, icon, href, onClick } = props;\n\nconst Button = styled.button`\n display: flex;\n align-items: center;\n gap: 8px;\n\n border-radius: 4px;\n background: #04a46e;\n\n color: #f4f4f4;\n font-size: 16px;\n font-style: normal;\n font-weight: 700;\n line-height: 20px; /* 125% */\n\n padding: 0.5rem 1rem;\n\n &:hover {\n background: #555555;\n text-decoration: none !important;\n }\n\n outline: none;\n border: none;\n`;\n\nreturn (\n <div className={`d-flex flex-row-reverse ${props.className}`}>\n {props.href ? (\n <Link to={props.href} style={{ textDecoration: \"none\" }}>\n <Button\n className=\"community-control\"\n data-testid={props.testId ? props.testId : \"\"}\n >\n <i className={props.icon ? props.icon : \"bi bi-plus-circle-fill\"}></i>\n {props.title}\n </Button>\n </Link>\n ) : (\n <Button\n onClick={props.onClick}\n className=\"community-control\"\n data-testid={props.testId ? props.testId : \"\"}\n >\n <i className={props.icon ? props.icon : \"bi bi-plus-circle-fill\"}></i>\n {props.title || \"Community\"}\n </Button>\n )}\n </div>\n);\n" }, "devhub.components.molecule.Input": { "": "const TextInput = ({\n className,\n format,\n inputProps: { className: inputClassName, ...inputProps },\n key,\n label,\n multiline,\n onChange,\n placeholder,\n type,\n value,\n skipPaddingGap,\n style,\n error,\n ...otherProps\n}) => {\n State.init({\n data: value,\n error: error,\n });\n\n useEffect(() => {\n const inputError = \"\";\n if (value !== state.data) {\n // check for input number error (since type: number doesn't work on firefox/safari)\n if (inputProps.inputmode === \"numeric\") {\n const inputValue = state.data;\n if (!inputValue) {\n return;\n }\n let isValidInteger = /^[1-9][0-9]*$/.test(inputValue);\n if (!isValidInteger) {\n inputError = \"Please enter the nearest positive whole number.\";\n }\n State.update({ error: inputError });\n }\n const handler = setTimeout(() => {\n onChange({ target: { value: state.data }, error: inputError });\n }, 30);\n\n return () => {\n clearTimeout(handler);\n };\n }\n }, [state.data]);\n\n useEffect(() => {\n if (value !== state.data) {\n State.update({ data: value });\n }\n }, [value]);\n\n useEffect(() => {\n if (error !== state.error) {\n State.update({ error: error });\n }\n }, [error]);\n\n const typeAttribute =\n type === \"text\" ||\n type === \"password\" ||\n type === \"number\" ||\n type === \"date\"\n ? type\n : \"text\";\n\n const isValid = () => {\n if (!state.data || state.data.length === 0) {\n return !inputProps.required;\n } else if (inputProps.min && inputProps.min > state.data?.length) {\n return false;\n } else if (inputProps.max && inputProps.max < state.data?.length) {\n return false;\n } else if (\n inputProps.allowCommaAndSpace === false &&\n /^[^,\\s]*$/.test(state.data) === false\n ) {\n return false;\n } else if (\n inputProps.validUrl === true &&\n /^(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)$/.test(\n state.data\n ) === false\n ) {\n return false;\n }\n return true;\n };\n\n const renderedLabels = [\n (label?.length ?? 0) > 0 ? (\n <span className=\"d-inline-flex gap-1 text-wrap\">\n <span>{label}</span>\n\n {inputProps.required ? <span className=\"text-danger\">*</span> : null}\n </span>\n ) : null,\n\n format === \"markdown\" ? (\n <i class=\"bi bi-markdown text-muted\" title=\"Markdown\" />\n ) : null,\n\n format === \"comma-separated\" ? (\n <span\n className={`d-inline-flex align-items-center ${\n isValid() ? \"text-muted\" : \"text-danger\"\n }`}\n style={{ fontSize: 12 }}\n >\n {format}\n </span>\n ) : null,\n\n (inputProps.max ?? null) !== null ? (\n <span\n className={`d-inline-flex ${isValid() ? \"text-muted\" : \"text-danger\"}`}\n style={{ fontSize: 12 }}\n >{`${state.data?.length ?? 0} / ${inputProps.max}`}</span>\n ) : null,\n ].filter((label) => label !== null);\n\n const onKeyDown = props.onKeyDown ?? (() => {});\n\n return (\n <div\n className={[\n \"d-flex flex-column flex-1 align-items-start justify-content-evenly\",\n skipPaddingGap ? \"\" : \"gap-1 p-2\",\n className ?? \"\",\n ].join(\" \")}\n style={style}\n {...otherProps}\n >\n {renderedLabels.length > 0 ? (\n <span\n className=\"d-flex justify-content-between align-items-center gap-3 w-100\"\n id={key}\n >\n {renderedLabels.map((label) => label)}\n </span>\n ) : null}\n\n {!multiline ? (\n <div className=\"w-100\">\n <div className=\"input-group\">\n {inputProps.prefix && (\n <span className=\"input-group-text bg-white border-end-0\">\n {inputProps.prefix}\n </span>\n )}\n <input\n aria-describedby={key}\n data-testid={key}\n aria-label={label}\n className={[\n \"form-control border\",\n inputClassName,\n inputProps.prefix ? \"border-start-0\" : \"\",\n ].join(\" \")}\n type={typeAttribute}\n maxLength={inputProps.max}\n value={state.data}\n onChange={(e) => State.update({ data: e.target.value })}\n onBlur={(e) => {\n if (props.onBlur) {\n onBlur({ target: { value: e.target.value } });\n }\n }}\n onKeyDown={onKeyDown}\n {...{ placeholder, ...inputProps }}\n />\n </div>\n {state.error && (\n <div style={{ color: \"red\" }} className=\"text-sm\">\n {state.error}\n </div>\n )}\n </div>\n ) : (\n <textarea\n aria-describedby={key}\n data-testid={key}\n aria-label={label}\n className={[\"form-control border\", inputClassName].join(\" \")}\n placeholder={\n placeholder + (inputProps.required ? \" ( required )\" : \"\")\n }\n style={{ resize: inputProps.resize ?? \"vertical\" }}\n type={typeAttribute}\n maxLength={inputProps.max}\n value={state.data}\n onChange={(e) => State.update({ data: e.target.value })}\n onBlur={(e) => {\n if (props.onBlur) {\n onBlur({ target: { value: e.target.value } });\n }\n }}\n onKeyDown={onKeyDown}\n {...{ placeholder, ...inputProps }}\n />\n )}\n </div>\n );\n};\n\nreturn TextInput(props);\n" }, "devhub.entity.proposal.CommentIcon": { "": "const item = props.item;\nconst showOverlay = props.showOverlay ?? true;\n\nif (!props.hideCount && !item) {\n return \"\";\n}\n\nconst comments = !props.hideCount && Social.index(\"comment\", item);\nconst dataLoading = props.hideCount ? false : comments === null;\nconst totalComments = comments?.length || 0;\n\nconst CommentButton = styled.button`\n border: 0;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n color: #687076;\n font-weight: 400;\n font-size: 14px;\n line-height: 17px;\n cursor: pointer;\n background: none;\n padding: 6px;\n transition: color 200ms;\n\n i {\n font-size: 16px;\n transition: color 200ms;\n }\n\n &:hover,\n &:focus {\n outline: none;\n color: #11181c;\n }\n`;\n\nreturn (\n <CommentButton\n disabled={dataLoading || !context.accountId}\n title={showOverlay && \"Add Comment\"}\n onClick={props.onClick}\n >\n <i className=\"bi-chat\" />\n {!props.hideCount && totalComments}\n </CommentButton>\n);\n" }, "devhub.entity.team.LabelPermissions": { "": "const { identifier, editPost, setEditPost, useLabels, setUseLabels, disabled } =\n props;\n\nreturn (\n <>\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n value={editPost}\n id={`editPostCheckbox${identifier}`}\n checked={editPost}\n onChange={() => setEditPost(!editPost)}\n disabled={disabled}\n />\n <label class=\"form-check-label\" for={`editPostCheckbox${identifier}`}>\n This team is allowed to edit-post with this / these labels\n </label>\n </div>\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n value={useLabels}\n id={`useLabelsCheckbox${identifier}`}\n checked={useLabels}\n onChange={() => setUseLabels(!useLabels)}\n disabled={disabled}\n />\n <label class=\"form-check-label\" for={`useLabelsCheckbox${identifier}`}>\n Only this team and moderators are allowed to use this label\n </label>\n </div>\n </>\n);\n" }, "devhub.entity.addon.blog.Page": { "": "const { getAccountCommunityPermissions } = VM.require(\n \"devhub.megha19.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.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"\n }\n props={{ text: content }}\n />\n </Container>\n </>\n );\n}\n\nreturn { Page };\n" }, "devhub.page.admin.index": { "": "const { hasModerator, getRootMembers, getAccessControlInfo } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nif (!hasModerator || !getRootMembers || !getAccessControlInfo) {\n return <p>Loading modules...</p>;\n}\n\nconst accessControlInfo = getAccessControlInfo();\n\nif (!accessControlInfo.members_list) {\n return <p>Loading members list...</p>;\n}\n\nconst rootMembers = getRootMembers();\nconst teamNames = Object.keys(rootMembers || {});\n\nconst isDevHubModerator = hasModerator({\n account_id: context.accountId,\n});\n\nconst noPermissionBanner = (\n <div\n className=\"d-flex flex-column justify-content-center align-items-center\"\n style={{ height: 384 }}\n >\n <h2 className=\"alert alert-danger\">\n Your account does not have administration permissions.\n </h2>\n </div>\n);\n\nif (!isDevHubModerator) {\n return noPermissionBanner;\n}\n\nfunction createEditTeam({\n teamName,\n label,\n editPost,\n useLabels,\n members,\n contractCall, // typescript edit_member || add_member\n}) {\n let txn = [];\n console.log(\"🚀 ~ file: index.jsx:48 ~ teamNames:\", teamNames);\n if (teamNames.includes(`team:${teamName}`) && contractCall === \"add_member\") {\n return setAlertMessage(\"This team name already exists\");\n }\n const allLabels = Object.keys(accessControlInfo.rules_list);\n if (allLabels.includes(label) && contractCall === \"add_member\") {\n return setAlertMessage(\"This label is already restricted by another team\");\n }\n\n const membersAndTeams = Object.keys(accessControlInfo.members_list);\n members.forEach((member) => {\n // if Contract panic member does not exist in the members_list\n if (!membersAndTeams.includes(member)) {\n // Add member\n txn.push({\n contractName: \"devhub.near\",\n methodName: \"add_member\",\n args: {\n member: member,\n metadata: {\n member_metadata_version: \"V0\",\n description: \"\",\n permissions: {},\n children: [],\n parents: [],\n },\n },\n gas: Big(10).pow(14),\n });\n }\n });\n\n // Check edit team\n Near.call([\n ...txn,\n {\n contractName: \"devhub.near\",\n methodName: contractCall, // add_member || edit_member\n args: {\n member: `team:${teamName}`,\n metadata: {\n member_metadata_version: \"V0\",\n description: \"\",\n permissions: {\n [label]: [\n ...(editPost ? [\"edit-post\"] : []),\n ...(useLabels ? [\"use-labels\"] : []),\n ],\n },\n children: members,\n parents: [],\n },\n },\n gas: Big(10).pow(14),\n },\n ]);\n}\n\nconst Container = styled.div`\n width: 100%;\n margin: 0 auto;\n padding: 20px;\n text-align: left;\n`;\n\nconst Tab = styled.button`\n color: rgb(0, 236, 151);\n &:hover {\n color: rgba(0, 236, 151, 0.5);\n }\n`;\n\nreturn (\n <Container>\n <div className=\"d-flex flex-column gap-4 p-4\">\n <ul class=\"nav nav-tabs\" id=\"myTab\" role=\"tablist\">\n <li class=\"nav-item\" role=\"presentation\">\n <Tab\n className=\"nav-link active\"\n id=\"home-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#home\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"home\"\n aria-selected=\"true\"\n >\n Home page settings\n </Tab>\n </li>\n <li class=\"nav-item\" role=\"presentation\">\n <Tab\n className=\"nav-link\"\n id=\"profile-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#profile\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"profile\"\n aria-selected=\"false\"\n >\n Moderators\n </Tab>\n </li>\n <li class=\"nav-item\" role=\"presentation\">\n <Tab\n className=\"nav-link\"\n id=\"contact-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#contact\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"contact\"\n aria-selected=\"false\"\n >\n Restricted labels\n </Tab>\n </li>\n </ul>\n <div class=\"tab-content\" id=\"myTabContent\">\n <div\n class=\"tab-pane fade show active\"\n id=\"home\"\n role=\"tabpanel\"\n aria-labelledby=\"home-tab\"\n >\n <Widget src=\"devhub.megha19.near/widget/devhub.page.admin.homepageTab\" />\n </div>\n <div\n class=\"tab-pane fade\"\n id=\"profile\"\n role=\"tabpanel\"\n aria-labelledby=\"profile-tab\"\n >\n <Widget\n src=\"devhub.megha19.near/widget/devhub.page.admin.moderatorsTab\"\n props={{\n accessControlInfo,\n createEditTeam,\n }}\n />\n </div>\n <div\n class=\"tab-pane fade\"\n id=\"contact\"\n role=\"tabpanel\"\n aria-labelledby=\"contact-tab\"\n >\n <Widget\n src=\"devhub.megha19.near/widget/devhub.page.admin.restrictedLabelsTab\"\n props={{\n accessControlInfo,\n createEditTeam,\n teamNames,\n }}\n />\n </div>\n </div>\n </div>\n </Container>\n);\n" }, "devhub.page.proposals": { "": "return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.Feed\"}\n props={{}}\n />\n);\n" }, "devhub.components.atom.Icon": { "": "const svgIconsByVariant = {\n floppy_drive: (elementProps) => (\n <svg\n fill=\"#ffffff\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16px\"\n height=\"16px\"\n viewBox=\"0 0 353.073 353.073\"\n {...elementProps}\n >\n <g>\n <path\n d={[\n \"M340.969,0H12.105C5.423,0,0,5.423,0,12.105v328.863c0,6.68,5.423,12.105,12.105,12.105h328.864\",\n \"c6.679,0,12.104-5.426,12.104-12.105V12.105C353.073,5.423,347.647,0,340.969,0z\",\n \"M67.589,18.164h217.895v101.884H67.589V18.164z\",\n \"M296.082,327.35H57.003V176.537h239.079V327.35z\",\n \"M223.953,33.295h30.269v72.638h-30.269V33.295z\",\n \"M274.135,213.863H78.938v-12.105\",\n \"h195.197V213.863z\",\n \"M274.135,256.231H78.938v-12.105h195.197V256.231z\",\n \"M274.135,297.087H78.938v-12.105h195.197V297.087z\",\n ].join(\" \")}\n />\n </g>\n </svg>\n ),\n};\n\nconst iconsByType = {\n bootstrap_icon: ({ className, variant, ...otherProps }) => (\n <i className={`bi ${variant} ${className}`} {...otherProps} />\n ),\n\n svg_icon: ({ variant, ...elementProps }) =>\n svgIconsByVariant[variant](elementProps),\n};\n\nconst Icon = ({ type, ...otherProps }) =>\n typeof iconsByType[type] === \"function\"\n ? iconsByType[type](otherProps)\n : null;\n\nreturn Icon(props);\n" }, "devhub.entity.post.Postv2": { "": "const { getPost } =\n VM.require(\"devhub.megha19.near/widget/core.adapter.devhub-contract\") ||\n (() => {});\n\nconst { postKey, template } = props;\n\nconst post = getPost({ post_id: parseInt(postKey) });\n\nif (!post) {\n return <div>Loading ...</div>;\n}\n\nconst Template = template || (() => <></>);\n\nreturn (\n <Template\n labels={post.snapshot.labels}\n data={JSON.parse(post.snapshot.description || \"null\") || {}}\n />\n);\n" }, "devhub.components.molecule.ProfileLine": { "": "const accountId = props.accountId ?? context.accountId;\n\nreturn (\n <span>\n <Widget\n src=\"mob.near/widget/ProfileLine\"\n props={{\n ...props,\n accountId,\n link: `#/mob.near/widget/ProfilePage?accountId=${accountId}`,\n }}\n />\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.BadgesList\"\n props={{\n accountId,\n mode: \"compact\",\n }}\n />\n </span>\n);\n" }, "devhub.entity.addon.telegram.Viewer": { "": "const { handles } = props;\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 (!handles || handles.length === 0) {\n return (\n <CenteredMessage height={\"384px\"}>\n <h2>No Telegram Configured</h2>\n </CenteredMessage>\n );\n} else {\n return (\n <div>\n {(handles || []).map((tg) => {\n const pattern = /https:\\/\\/t.me\\/(.*)/;\n const includesHttp = tg.match(pattern);\n const handle = includesHttp ? includesHttp[1] : tg;\n return (\n <>\n <iframe\n iframeResizer\n src={\n \"https://j96g3uepe0.execute-api.us-east-1.amazonaws.com/groups-ui/\" +\n handle\n }\n frameborder=\"0\"\n // width and minWidth required by iframeResizer\n style={{\n width: \"1px\",\n minWidth: \"100%\",\n marginTop: \"20px\",\n }}\n ></iframe>\n\n <a href={includesHttp ? tg : \"https://t.me/\" + tg} target=\"_blank\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-primary\" },\n label: \"View More\",\n }}\n />\n </a>\n </>\n );\n })}\n </div>\n );\n}\n" }, "app": { "": "/**\n * This is the main entry point for the DevHub application.\n * Page route gets passed in through params, along with all other page props.\n */\n\nconst { onDraftStateChange } = VM.require(\n \"devhub.megha19.near/widget/devhub.entity.post.draft\"\n);\n\nconst { page, ...passProps } = props;\n\n// Import our modules\nconst { AppLayout } = VM.require(\n \"devhub.megha19.near/widget/devhub.components.templates.AppLayout\"\n);\n\nif (!AppLayout) {\n return <p>Loading modules...</p>;\n}\n\n// CSS styles to be used across the app.\n// Define fonts here, as well as any other global styles.\nconst Theme = styled.div`\n a {\n color: 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\nif (!page) {\n // If no page is specified, we default to the feed page TEMP\n page = \"home\";\n}\n\n// Track visits\n\nif (\"${REPL_POSTHOG_API_KEY}\".length === 47) {\n useEffect(() => {\n const hashedUserId = context.accountId\n ? Array.from(nacl.hash(Buffer.from(context.accountId)))\n .map((b) => (\"00\" + b.toString(16)).slice(-2))\n .join(\"\")\n : \"unauthenticated\";\n\n fetch(\"https://eu.posthog.com/capture/\", {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n },\n\n body: JSON.stringify({\n api_key: \"${REPL_POSTHOG_API_KEY}\",\n event: \"devhub_pageview\",\n properties: {\n distinct_id: hashedUserId,\n page,\n ...props,\n },\n timestamp: new Date().toISOString(),\n }),\n });\n }, [props]);\n}\n\n// This is our navigation, rendering the page based on the page parameter\nfunction Page() {\n const routes = page.split(\".\");\n switch (routes[0]) {\n case \"home\": {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.page.home\"\n props={passProps}\n />\n );\n }\n // ?page=communities\n case \"communities\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.communities\"}\n props={passProps}\n />\n );\n }\n case \"announcements\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.announcements\"}\n props={passProps}\n />\n );\n }\n\n // ?page=community\n case \"community\": {\n return (\n // Considering to consolidate this into a single widget,\n // where each level handles its own routing.\n // Modularizing a page just like we do with addons\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.community.Provider\"}\n props={{\n ...passProps,\n Children: (p) => {\n // passing props from the Provider into the Children\n switch (routes[1]) {\n // ?page=community.configuration\n case \"configuration\": {\n return (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.page.community.configuration\"\n }\n props={{\n ...passProps,\n ...p,\n }}\n />\n );\n }\n // ?page=community\n default: {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.community.index\"}\n props={{\n ...passProps,\n ...p,\n }}\n />\n );\n }\n }\n },\n }}\n />\n );\n }\n // ?page=feed\n case \"feed\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.feed\"}\n props={passProps}\n />\n );\n }\n // ?page=create\n case \"create\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.post.PostEditor\"}\n props={{ ...passProps, isCreatePostPage: true, onDraftStateChange }}\n />\n );\n }\n\n case \"create-proposal\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.Editor\"}\n props={{ ...passProps }}\n />\n );\n }\n\n case \"proposals\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.proposals\"}\n props={passProps}\n />\n );\n }\n case \"proposal\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.Proposal\"}\n props={passProps}\n />\n );\n }\n // ?page=about\n case \"about\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.about\"}\n props={passProps}\n />\n );\n }\n case \"contribute\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.contribute\"}\n props={passProps}\n />\n );\n }\n case \"profile\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.profile\"}\n props={passProps}\n />\n );\n }\n // ?page=blog\n case \"blog\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.blog\"}\n props={passProps}\n />\n );\n }\n case \"post\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.post\"}\n props={passProps}\n />\n );\n }\n case \"admin\": {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.page.admin.index\"}\n props={passProps}\n />\n );\n }\n default: {\n // TODO: 404 page\n return <p>404</p>;\n }\n }\n}\n\nreturn (\n <Theme>\n <AppLayout page={page}>\n <Page />\n </AppLayout>\n </Theme>\n);\n" }, "devhub.page.admin.homepageTab": { "": "const {\n getFeaturedCommunities,\n setFeaturedCommunities,\n getAllCommunitiesMetadata,\n} = VM.require(\"devhub.megha19.near/widget/core.adapter.devhub-contract\");\n\nconst { Tile } =\n VM.require(\"devhub.megha19.near/widget/devhub.components.molecule.Tile\") ||\n (() => <></>);\n\nif (\n !getFeaturedCommunities ||\n !setFeaturedCommunities ||\n !getAllCommunitiesMetadata ||\n !Tile\n) {\n return <p>Loading modules...</p>;\n}\n\nconst fc = getFeaturedCommunities();\n// The state will stay empty even after the data right data has been retrieved\nif (!fc) {\n return <p>Loading featured communities...</p>;\n}\nconst featuredCommunityList = fc || [];\n\nconst allCommunities = getAllCommunitiesMetadata();\n\nconst [communityMessage, setCommunityMessage] = useState(\"\");\nconst [previewConnect, setPreviewConnect] = useState(false);\n\nconst [communityHandles, setCommunityHandles] = useState(\n featuredCommunityList.map(({ handle }) => handle)\n);\nconst handleResetItems = () => {\n setCommunityHandles(featuredCommunityList.map(({ handle }) => handle));\n};\n\nfunction handleSubmit() {\n if (communityHandles.length < 4) {\n return setCommunityMessage(\"Can't set fewer than 4 communities\");\n }\n setFeaturedCommunities({ handles: communityHandles });\n}\n\nreturn (\n <>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.atom.Alert\"\n props={{\n onClose: () => setCommunityMessage(\"\"),\n message: communityMessage,\n }}\n />\n <Tile className=\"p-3 mb-3\">\n <h3> Manage featured communities</h3>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.ListEditor\"\n props={{\n data: {\n maxLength: 5,\n placeholder: \"Community handle\",\n prefix: \"Community handle\",\n list: communityHandles,\n },\n setList: setCommunityHandles,\n validate: (newItem) => {\n return allCommunities.map(({ handle }) => handle).includes(newItem);\n },\n invalidate: () =>\n setCommunityMessage(\n \"This community handle does not exist, make sure you use an existing handle.\"\n ),\n }}\n />\n\n <div\n className={\"d-flex align-items-center justify-content-end gap-3 mt-4\"}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: {\n root: \"btn-outline-danger shadow-none border-0\",\n },\n label: \"Cancel\",\n onClick: handleResetItems,\n }}\n />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn\" },\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Submit\",\n onClick: handleSubmit,\n }}\n />\n </div>\n </Tile>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.PostControls\"}\n props={{\n onClick: () => setPreviewConnect(!previewConnect),\n icon: previewConnect ? \"bi bi-toggle-on\" : \"bi bi-toggle-off\",\n title: \"Preview homepage\",\n testId: \"preview-homepage\",\n }}\n />\n <div class=\"mt-3\">\n {previewConnect && (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.island.connect\"\n props={{ ...props }}\n />\n )}\n </div>\n </>\n);\n" }, "devhub.page.blog": { "": "const { id } = props;\n\nconst { Page } =\n VM.require(\"devhub.megha19.near/widget/devhub.entity.addon.blog.Page\") ||\n (() => <></>);\n\nconst [showEditScreenData, setShowEditScreen] = useState(null);\n\nif (id && !showEditScreenData) {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.post.Postv2\"\n props={{\n postKey: id,\n template: (p) => (\n <Page\n {...(p || {})}\n onEdit={() => {\n setShowEditScreen({ ...p, data: { ...p.data, id: id } });\n }}\n accountId={context.accountId}\n />\n ),\n }}\n />\n );\n}\n\nconst HeaderContainer = styled.div`\n display: flex;\n width: 100%;\n height: 60px;\n padding: 1rem 3rem;\n align-items: center;\n flex-shrink: 0;\n background-color: #f4f4f4;\n\n @media screen and (max-width: 768px) {\n padding: 1rem;\n }\n`;\n\nconst Header = styled.h1`\n color: #555555;\n font-size: 24px;\n font-style: normal;\n font-weight: 500;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.24px;\n margin: 0;\n`;\n\nconst BlogContainer = styled.div`\n padding: 1rem 3rem;\n\n @media screen and (max-width: 768px) {\n padding: 1rem;\n }\n`;\n\nconst EditorContainer = styled.div`\n position: relative;\n width: 100%;\n padding: 20px;\n .cancel-icon {\n position: absolute;\n top: 30px;\n right: 30px;\n font-size: 25px;\n cursor: pointer;\n }\n`;\n\n// I like that this reduces duplicate code with the Viewer, but I don't like\n// that \"Latest Blog Posts\" carries over... // TOOD: create a common blog\n// feed... I think the addon.blog.Feed naming is confusing, as this should be a\n// generic feed component.\n\nif (showEditScreenData) {\n return (\n <EditorContainer>\n <div className=\"cancel-icon\" onClick={() => setShowEditScreen(null)}>\n <i class=\"bi bi-x-circle\"></i>\n </div>\n <Widget\n src={`devhub.megha19.near/widget/devhub.entity.addon.blog.Configurator`}\n props={{\n ...showEditScreenData,\n handle: showEditScreenData?.labels?.[1], // community-handle\n }}\n />\n </EditorContainer>\n );\n}\nreturn (\n <div className=\"w-100\">\n <Widget src={`devhub.megha19.near/widget/devhub.components.island.banner`} />\n <HeaderContainer>\n <Header>Blog</Header>\n </HeaderContainer>\n <BlogContainer>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.addon.blog.Viewer\"}\n props={{\n handle: \"developer-dao\",\n hideTitle: true,\n }}\n />\n </BlogContainer>\n </div>\n);\n" }, "devhub.entity.addon.blog.Feed": { "": "const { Item, Layout } = props;\n\nLayout = Layout || (() => <></>);\n\nconst Container = styled.div``;\n\nconst Loader = styled.div`\n text-align: center;\n padding: 20px;\n`;\n\nconst QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql/`;\nconst DISPLAY_COUNT = 10;\n\nconst fetchGraphQL = (operationsDoc, operationName, variables) => {\n return asyncFetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `bo_near` },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n};\n\nconst queryName =\n props.queryName ?? `bo_near_devhub_v38_posts_with_latest_snapshot`;\n\nconst query = `query DevhubPostsQuery($limit: Int = 100, $offset: Int = 0, $where: ${queryName}_bool_exp = {}) {\n ${queryName}(\n limit: $limit\n offset: $offset\n order_by: {ts: desc}\n where: $where\n ) {\n post_id\n }\n }\n`;\n\nconst [postIds, setPostIds] = useState([]);\nconst [loading, setLoading] = useState(false);\nconst [cachedItems, setCachedItems] = useState({});\nconst [hasNext, setHasNext] = useState(true);\n\nconst buildWhereClause = () => {\n let where = {};\n if (props.author) {\n where = { author_id: { _eq: props.author }, ...where };\n }\n if (props.term) {\n where = { description: { _ilike: `%${props.term}%` }, ...where };\n }\n if (props.includeLabels && Array.isArray(props.includeLabels)) {\n const labelConditions = props.includeLabels.map((label) => ({\n labels: { _contains: label },\n }));\n\n where = { _and: [...labelConditions, where] };\n }\n if (props.excludeLabels && Array.isArray(props.excludeLabels)) {\n const labelConditions = props.excludeLabels.map((label) => ({\n labels: { _nin: label },\n }));\n\n where = { _and: [...labelConditions, where] };\n }\n if (!props.recency) {\n where = { parent_id: { _is_null: true }, ...where };\n }\n return where;\n};\n\nconst fetchPostIds = (offset) => {\n if (!offset) {\n offset = 0;\n }\n if (loading) return;\n setLoading(true);\n const variables = { limit: DISPLAY_COUNT, offset, where: buildWhereClause() };\n fetchGraphQL(query, \"DevhubPostsQuery\", variables).then((result) => {\n if (result.status === 200) {\n if (result.body.data) {\n const data = result.body.data[queryName];\n const newPostIds = data.map((p) => p.post_id);\n setPostIds(offset === 0 ? newPostIds : [...postIds, ...newPostIds]);\n setHasNext(data.length >= variables.limit);\n } else {\n console.error(\"GraphQL Error:\", result.errors);\n }\n setLoading(false);\n }\n });\n};\n\nuseEffect(() => {\n fetchPostIds();\n}, [props.author, props.term, props.tag, props.recency]);\n\nconst handleLoadMore = () => {\n if (!hasNext) return;\n fetchPostIds(postIds.length);\n};\n\nconst renderLoader = () => <Loader>Loading...</Loader>;\n\nconst renderItem = (postId) => (\n <div key={postId}>\n {(props.renderItem && props.renderItem(postId)) || <div>Post {postId}</div>}\n </div>\n);\n\nconst cachedRenderItem = (postId) => {\n if (!(postId in cachedItems)) {\n cachedItems[postId] = renderItem(postId);\n setCachedItems({ ...cachedItems });\n }\n return cachedItems[postId];\n};\n\nreturn (\n <Container>\n {loading && renderLoader()}\n {postIds.length > 0 ? (\n <InfiniteScroll\n pageStart={0}\n dataLength={postIds.length}\n loadMore={handleLoadMore}\n hasMore={hasNext}\n loader={renderLoader()}\n >\n <Layout>\n {/* Layout */}\n {postIds.map(cachedRenderItem)}\n </Layout>\n </InfiniteScroll>\n ) : (\n <p class=\"text-secondary\">No posts</p>\n )}\n </Container>\n);\n" }, "devhub.entity.proposal.StatusTag": { "": "const timelineStatus = props.timelineStatus;\nconst size = props.size ?? \"md\";\n\nconst getClassNameByStatus = () => {\n switch (timelineStatus) {\n case \"DRAFT\":\n return \"grey\";\n case \"REVIEW\":\n return \"blue\";\n case \"APPROVED\":\n case \"APPROVED_CONDITIONALLY\":\n case \"FUNDED\":\n return \"green\";\n case \"PAYMENT_PROCESSING\":\n return \"orange\";\n case \"REJECTED\":\n case \"CANCELLED\":\n return \"warning\";\n default:\n return \"green\";\n }\n};\n\nconst Container = styled.div`\n font-size: ${({ size }) => {\n switch (size) {\n case \"sm\":\n return \"10px\";\n case \"lg\":\n return \"14px\";\n default:\n return \"12px\";\n }\n }};\n\n min-width: fit-content;\n\n .orange-tag {\n border: 1px solid #ff7a00 !important;\n color: #ff7a00 !important;\n }\n\n .warning-tag {\n border: 1px solid #f40303 !important;\n color: #f40303 !important;\n }\n\n .blue-tag {\n border: 1px solid #2c3e50 !important;\n color: #2c3e50 !important;\n }\n\n .grey-tag {\n border: 1px solid #979797 !important;\n color: #979797 !important;\n }\n\n .green-tag {\n border: 1px solid #04a46e !important;\n color: #04a46e !important;\n }\n\n .fw-bold {\n font-weight: 600 !important;\n }\n`;\n\nreturn (\n <Container size={size}>\n <div className={getClassNameByStatus() + \"-tag fw-bold rounded-2 p-1 px-2\"}>\n {(timelineStatus ?? \"\").replace(\"_\", \" \")}\n </div>\n </Container>\n);\n" }, "devhub.entity.addon.blog.editor.index": { "": "let theme = props.theme;\nlet variables = props.variables;\nconst editData = props.data;\n\nif (!variables) {\n variables = ``;\n}\n\nif (!theme) {\n theme = ``;\n}\n\nconst Root = styled.div`\n ${variables}\n ${theme}\n\n a {\n text-decoration: none;\n color: var(--base900);\n }\n`;\n\nconst Container = styled.div`\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n width: 100%;\n`;\n\n// What would be the first steps?\n// Create this container and an empty provider\n// Provider has some items, onChange, onSubmit console logs\n// Create a layout that takes in Editor\n\nreturn (\n <Root>\n {/* Get any layout */}\n <Container>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.addon.blog.editor.provider\"\n props={{\n handle: props.handle,\n Layout: (providerProps) => {\n const { data, onChange, onSubmit, onCancel, getData } =\n providerProps;\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.addon.blog.editor.layout\"\n props={{\n getData,\n editData: editData,\n Sidebar: (p) => (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.addon.blog.editor.sidebar\"\n props={{\n ...p,\n ...providerProps,\n }}\n />\n ),\n Content: (p) => (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.addon.blog.editor.content\"\n props={{\n onChange,\n onCancel,\n onSubmit,\n ...p,\n }}\n />\n ),\n }}\n />\n );\n },\n }}\n />\n </Container>\n </Root>\n);\n" }, "devhub.components.molecule.Button": { "": "const styles = `\n padding: 0.5rem 1.2rem !important;\n min-height: 36px;\n line-height: 1.5;\n text-decoration: none !important;\n\n &:not(.shadow-none) {\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n transition: box-shadow 0.6s;\n }\n\n &.btn-sm {\n padding: 0.5rem 0.8rem !important;\n min-height: 32px;\n line-height: 1;\n }\n\n &.btn-lg {\n padding: 1rem 1.5rem !important;\n min-height: 48px;\n }\n\n &.btn-primary {\n border: none;\n --bs-btn-color: #ffffff;\n --bs-btn-bg: #087990;\n --bs-btn-border-color: #087990;\n --bs-btn-hover-color: #ffffff;\n --bs-btn-hover-bg: #055160;\n --bs-btn-hover-border-color: #055160;\n --bs-btn-focus-shadow-rgb: 49, 132, 253;\n --bs-btn-active-color: #ffffff;\n --bs-btn-active-bg: #055160;\n --bs-btn-active-border-color: #055160;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n --bs-btn-disabled-color: #ffffff;\n --bs-btn-disabled-bg: #0551604a;\n }\n\n\t&.btn-outline-primary {\n\t\t--bs-btn-color: #087990;\n\t\t--bs-btn-border-color: #087990;\n\t\t--bs-btn-hover-color: #ffffff;\n\t\t--bs-btn-hover-bg: #087990;\n\t\t--bs-btn-hover-border-color: #087990;\n\t\t--bs-btn-focus-shadow-rgb: 49, 132, 253;\n\t\t--bs-btn-active-color: #ffffff;\n\t\t--bs-btn-active-bg: #087990;\n\t\t--bs-btn-active-border-color: #087990;\n\t\t--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n\t\t--bs-btn-disabled-border-color: #0551604a;\n\t}\n\n &[class*=\"btn-outline-\"] {\n border-width: 2px;\n }\n\n &.btn-outline-primary {\n --bs-btn-disabled-color: #6c757d8f;\n }\n\n &.btn-secondary {\n border: none;\n }\n\n &.btn-outline-secondary {\n --bs-btn-disabled-color: #6c757d8f;\n }\n\n &.btn-success {\n border: none;\n --bs-btn-disabled-bg: #35482a4a;\n }\n\n &.btn-outline-success {\n --bs-btn-disabled-color: #6c757d8f;\n }\n\n &.btn-danger {\n border: none;\n }\n\n &.btn-outline-danger {\n --bs-btn-disabled-color: #6c757d8f;\n }\n\n &.btn-warning {\n border: none;\n }\n\n &.btn-outline-warning {\n --bs-btn-disabled-color: #6c757d8f;\n }\n\n &.btn-info {\n border: none;\n }\n\n &.btn-outline-info {\n --bs-btn-disabled-color: #6c757d8f;\n }\n`;\n\nconst rootElementByType = (type) =>\n type === \"link\"\n ? styled.a`\n ${styles}\n `\n : styled.button`\n ${styles}\n `;\n\nconst Button = ({\n classNames,\n icon: iconProps,\n label,\n type,\n isHidden,\n notRounded,\n ...restProps\n}) => {\n const ButtonRoot = rootElementByType(type);\n\n return (\n <ButtonRoot\n className={[\n \"btn d-inline-flex align-items-center gap-2\",\n classNames?.root ?? \"btn-primary\",\n !notRounded ?? \"rounded-pill\",\n isHidden ?? false ? \"d-none\" : \"\",\n ].join(\" \")}\n style={{ width: \"fit-content\" }}\n {...restProps}\n >\n {iconProps !== null &&\n typeof iconProps === \"object\" &&\n !Array.isArray(iconProps) && (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.atom.Icon\"}\n props={iconProps}\n />\n )}\n <span className={classNames?.label} style={{ lineHeight: \"inherit\" }}>\n {label}\n </span>\n </ButtonRoot>\n );\n};\n\nreturn Button(props);\n" }, "devhub.components.organism.Feed": { "": "const { Feed } = VM.require(\"devs.near/widget/Feed\");\nFeed = Feed || (() => <></>);\nconst setPostExists = props.setPostExists ?? (() => {});\nconst showFlagAccountFeature = props.showFlagAccountFeature ?? false;\n\nconst filteredAccountIds = props.filteredAccountIds ?? [];\n\nconst GRAPHQL_ENDPOINT =\n props.GRAPHQL_ENDPOINT ?? \"https://near-queryapi.api.pagoda.co\";\n\nlet lastPostSocialApi = Social.index(\"post\", \"main\", {\n limit: 1,\n order: props.sort ? props.sort : \"desc\",\n});\n\nif (lastPostSocialApi == null) {\n return \"Loading...\";\n}\n\nState.init({\n // If QueryAPI Feed is lagging behind Social API, fallback to old widget.\n shouldFallback: false,\n});\n\nfunction fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(`${GRAPHQL_ENDPOINT}/v1/graphql`, {\n method: \"POST\",\n headers: { \"x-hasura-role\": \"dataplatform_near\" },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n}\n\nconst lastPostQuery = `\nquery IndexerQuery {\n dataplatform_near_social_feed_posts( limit: 1, order_by: { block_height: desc }) {\n block_height\n }\n}\n`;\n\nfetchGraphQL(lastPostQuery, \"IndexerQuery\", {})\n .then((feedIndexerResponse) => {\n if (\n feedIndexerResponse &&\n feedIndexerResponse.body.data.dataplatform_near_social_feed_posts.length >\n 0\n ) {\n const nearSocialBlockHeight = lastPostSocialApi[0].blockHeight;\n const feedIndexerBlockHeight =\n feedIndexerResponse.body.data.dataplatform_near_social_feed_posts[0]\n .block_height;\n\n const lag = nearSocialBlockHeight - feedIndexerBlockHeight;\n let shouldFallback = lag > 2 || !feedIndexerBlockHeight;\n if (shouldFallback === true) {\n console.log(\n \"Falling back to Social index feed. Block difference is: \",\n nearSocialBlockHeight - feedIndexerBlockHeight\n );\n State.update({ shouldFallback });\n }\n } else {\n console.log(\n \"Falling back to Social index feed. No QueryApi data received.\"\n );\n State.update({ shouldFallback: true });\n }\n })\n .catch((error) => {\n console.log(\n \"Error while fetching QueryApi feed (falling back to index feed): \",\n error\n );\n State.update({ shouldFallback: true });\n });\n\nreturn (\n <div>\n {state.shouldFallback ? (\n <Feed\n index={[\n {\n action: props.action ? props.action : \"post\",\n key: \"main\",\n options: {\n limit: 10,\n subscribe: props.onNewUnseenPosts ? true : false,\n order: props.sort ? props.sort : \"desc\",\n accountId: filteredAccountIds,\n },\n cacheOptions: {\n ignoreCache: true,\n },\n },\n ]}\n Item={(item) => {\n setPostExists(true);\n\n return (\n <Widget\n src=\"near/widget/v1.Posts.Post\"\n loading={<div className=\"w-100\" style={{ height: \"200px\" }} />}\n props={{\n accountId: item.accountId,\n blockHeight: item.blockHeight,\n filteredAccountIds: filteredAccountIds,\n }}\n />\n );\n }}\n />\n ) : (\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.organism.Feed.NearQueryApi`}\n props={{\n GRAPHQL_ENDPOINT,\n filteredAccountIds: filteredAccountIds,\n showFlagAccountFeature: showFlagAccountFeature,\n onNewUnseenPosts: props.onNewUnseenPosts,\n setPostExists: setPostExists,\n sort: props.sort,\n }}\n />\n )}\n </div>\n);\n" }, "devhub.components.molecule.AccountAutocomplete": { "": "if (!context.accountId || !props.term) return <></>;\n\nlet results = [];\nconst filterAccounts = props.filterAccounts ?? []; // hide certain accounts from the list\nconst profilesData = Social.get(\"*/profile/name\", \"final\") || {};\nconst followingData = Social.get(\n `${context.accountId}/graph/follow/**`,\n \"final\"\n);\nif (!profilesData) return <></>;\nconst profiles = Object.entries(profilesData);\nconst term = (props.term || \"\").replace(/\\W/g, \"\").toLowerCase();\nconst limit = 5;\n\nfor (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\nresults.sort((a, b) => b.score - a.score);\nresults = results.slice(0, limit);\nif (filterAccounts?.length > 0) {\n results = results.filter((item) => !filterAccounts?.includes(item.accountId));\n}\n\nfunction onResultClick(id) {\n props.onSelect && props.onSelect(id);\n}\n\nconst Wrapper = styled.div`\n position: relative;\n background: #eceef0;\n\n &::before {\n content: \"\";\n display: block;\n position: absolute;\n right: 0;\n width: 6px;\n height: 100%;\n background: linear-gradient(\n to left,\n rgba(236, 238, 240, 1),\n rgba(236, 238, 240, 0)\n );\n z-index: 10;\n }\n`;\n\nconst Scroller = styled.div`\n position: relative;\n display: flex;\n padding: 6px;\n gap: 6px;\n overflow: auto;\n scroll-behavior: smooth;\n align-items: center;\n scrollbar-width: none;\n -ms-overflow-style: none;\n &::-webkit-scrollbar {\n display: none;\n }\n\n > * {\n max-width: 175px;\n flex-grow: 0;\n flex-shrink: 0;\n\n button {\n border: 1px solid #eceef0;\n background: #fff !important;\n border-radius: 6px;\n padding: 3px 6px;\n transition: all 200ms;\n\n &:focus,\n &:hover {\n border-color: #687076;\n }\n }\n }\n`;\n\nconst CloseButton = styled.button`\n background: none;\n border: none;\n display: block;\n padding: 12px;\n color #687076;\n transition: all 200ms;\n\n &:hover {\n color: #000;\n }\n`;\n\nif (results.length === 0) return <></>;\n\nreturn (\n <Wrapper>\n <Scroller>\n <CloseButton tabIndex={-1} type=\"button\" onClick={props.onClose}>\n <i className=\"bi bi-x-circle\" />\n </CloseButton>\n\n {results.map((result) => {\n return (\n <Widget\n key={result.accountId}\n src=\"near/widget/AccountProfile\"\n props={{\n avatarSize: \"34px\",\n accountId: result.accountId,\n onClick: onResultClick,\n overlayPlacement: \"bottom\",\n }}\n />\n );\n })}\n </Scroller>\n </Wrapper>\n);\n" }, "devhub.components.feed.SubscribedFeed": { "": "const indexKey = props.indexKey ?? \"main\";\nconst groupId = props.groupId;\nconst permissions = props.permissions;\n\nconst index = [\n {\n action: \"post\",\n key: indexKey,\n options: {\n limit: 10,\n order: props.sort ? props.sort : \"desc\",\n subscribe: true,\n accountId: props.accounts,\n },\n cacheOptions: {\n ignoreCache: true,\n },\n },\n {\n action: \"repost\",\n key: indexKey,\n options: {\n limit: 10,\n order: props.sort ? props.sort : \"desc\",\n subscribe: true,\n accountId: props.accounts,\n },\n cacheOptions: {\n ignoreCache: true,\n },\n },\n];\n\nconst isPremiumFeed = props.isPremiumFeed;\nconst commentAccounts = props.commentAccounts;\nconst renderedPosts = {};\n\nconst makePostItem = (a) => ({\n type: \"social\",\n path: `${a.accountId}/post/main`,\n blockHeight: a.blockHeight,\n});\n\nconst renderPost = (a) => {\n if (a.value.type !== \"md\") {\n return false;\n }\n const item = JSON.stringify(makePostItem(a));\n if (item in renderedPosts) {\n return false;\n }\n renderedPosts[item] = true;\n\n return (\n <div key={JSON.stringify(a)} style={{ minHeight: \"150px\" }}>\n <Widget\n loading={<div className=\"w-100\" style={{ height: \"200px\" }} />}\n src=\"mob.near/widget/MainPage.N.Post\"\n props={{\n accountId: a.accountId,\n blockHeight: a.blockHeight,\n isPremiumFeed,\n commentAccounts,\n indexKey,\n groupId,\n permissions,\n }}\n />\n </div>\n );\n};\n\nconst repostSvg = (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n fill=\"currentColor\"\n viewBox=\"0 2 24 24\"\n stroke=\"currentColor\"\n strokeWidth=\"1\"\n >\n <path\n fill-rule=\"evenodd\"\n d=\"M4.854 1.146a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L4 2.707V12.5A2.5 2.5 0 0 0 6.5 15h8a.5.5 0 0 0 0-1h-8A1.5 1.5 0 0 1 5 12.5V2.707l3.146 3.147a.5.5 0 1 0 .708-.708l-4-4z\"\n transform=\"rotate(180, 12, 12), translate(0, 4)\"\n />\n <path\n fill-rule=\"evenodd\"\n d=\"M4.854 1.146a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L4 2.707V12.5A2.5 2.5 0 0 0 6.5 15h8a.5.5 0 0 0 0-1h-8A1.5 1.5 0 0 1 5 12.5V2.707l3.146 3.147a.5.5 0 1 0 .708-.708l-4-4z\"\n transform=\"translate(0, 4)\"\n />\n </svg>\n);\n\nconst extractParentPost = (item) => {\n if (!item || item.type !== \"social\" || !item.path || !item.blockHeight) {\n return undefined;\n }\n const accountId = item.path.split(\"/\")[0];\n return `${accountId}/post/main` === item.path\n ? { accountId, blockHeight: item.blockHeight }\n : undefined;\n};\n\nconst renderRepost = (a) => {\n if (a.value.type !== \"repost\") {\n return false;\n }\n const post = extractParentPost(a.value.item);\n if (!post) {\n return false;\n }\n const item = JSON.stringify(makePostItem(post));\n if (item in renderedPosts) {\n return false;\n }\n renderedPosts[item] = true;\n\n return (\n <div key={JSON.stringify(a)} style={{ minHeight: \"150px\" }}>\n <div\n className=\"text-muted\"\n style={{\n fontSize: \"13px\",\n fontWeight: 700,\n marginLeft: \"24px\",\n marginBottom: \"-24px\",\n paddingTop: \"4px\",\n position: \"relative\",\n zIndex: 1,\n }}\n >\n {repostSvg}{\" \"}\n <span style={{ marginLeft: \"8px\" }} data-testid=\"repost\">\n Reposted by{\" \"}\n <Widget\n loading={a.accountId}\n src=\"mob.near/widget/N.ProfileLine\"\n props={{\n accountId: a.accountId,\n hideImage: true,\n hideAccountId: true,\n tooltip: true,\n }}\n />\n </span>\n </div>\n <Widget\n loading={<div className=\"w-100\" style={{ height: \"200px\" }} />}\n src=\"mob.near/widget/MainPage.N.Post\"\n props={{\n accountId: post.accountId,\n blockHeight: post.blockHeight,\n reposted: true,\n isPremiumFeed,\n commentAccounts,\n indexKey,\n groupId,\n permissions,\n }}\n />\n </div>\n );\n};\n\nconst renderItem = (item) =>\n item.action === \"post\" ? renderPost(item) : renderRepost(item);\nreturn (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.feed.MergedIndexFeed\"\n props={{\n index,\n renderItem,\n filter: props.filter,\n threshold: props.threshold,\n }}\n />\n);\n" }, "devhub.components.atom.Alert": { "": "const Alert = ({ onClose, message }) =>\n message && (\n <div class=\"alert alert-warning alert-dismissible fade show\" role=\"alert\">\n {message}\n <button\n type=\"button\"\n class=\"btn-close\"\n data-bs-dismiss=\"alert\"\n aria-label=\"Close\"\n onClick={onClose}\n ></button>\n </div>\n );\n\nreturn Alert(props);\n" }, "devhub.components.island.banner": { "": "const imageLink =\n \"https://ipfs.near.social/ipfs/bafybeiap2mzwsly4apaldxguiunx4rjwqyadksj5yxuzwrww3kue3ao5qe\";\n\nconst HeroSection = styled.div`\n position: relative;\n height: auto;\n z-index: 3;\n width: 70%;\n background: #00ec97;\n clip-path: polygon(0 0, 100% 0%, 75% 100%, 0% 100%);\n\n padding-top: 2rem;\n padding-bottom: 2rem;\n padding-left: 3.375rem;\n\n @media screen and (max-width: 768px) {\n width: 100%;\n padding: 1rem 1.5rem;\n clip-path: none;\n }\n`;\n\nconst Title = styled.h1`\n color: #f4f4f4;\n font-size: 4rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 88px */\n letter-spacing: -1.76px;\n\n @media screen and (max-width: 768px) {\n font-size: 2.25rem;\n letter-spacing: -0.72px;\n margin: 0;\n }\n`;\n\nconst Container = styled.div`\n position: relative;\n width: 100%;\n height: max-content;\n overflow: hidden;\n\n @media screen and (max-width: 768px) {\n background: #f4f4f4;\n }\n`;\n\nconst ImageContainer = styled.div`\n width: 100%;\n height: 100%;\n position: absolute;\n top: 0;\n right: 0;\n z-index: 1;\n background: transparent;\n\n @media screen and (max-width: 768px) {\n display: none;\n }\n`;\n\nconst Image = styled.img`\n margin-left: 15.625rem;\n height: 100%;\n width: 100%;\n filter: grayscale(100%);\n object-fit: cover;\n`;\n\nconst MobileImage = styled.img`\n display: none;\n\n width: 100%;\n height: 196px;\n\n width: 100%;\n object-fit: cover;\n filter: grayscale(1);\n\n @media screen and (max-width: 768px) {\n display: block;\n }\n`;\n\nreturn (\n <Container>\n <HeroSection>\n <Title>\n The decentralized <br />\n <span style={{ color: \"#101820\" }}>home base</span> <br />\n for NEAR builders\n </Title>\n </HeroSection>\n <MobileImage src={imageLink} />\n <ImageContainer>\n <Image src={imageLink} />\n </ImageContainer>\n </Container>\n);\n" }, "devhub.components.molecule.NavbarDropdown": { "": "const title = props.title;\nconst links = props.links;\nconst href = props.href;\n\nconst [showMenu, setShowMenu] = useState(false);\n\nconst { href: linkHref } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nlinkHref || (linkHref = () => {});\n\nconst Dropdown = styled.div`\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: center;\n\n p {\n &.active {\n color: #fff;\n\n &:hover {\n text-decoration: none;\n color: #096d50 !important;\n }\n }\n }\n`;\n\nconst DropdownMenu = styled.div`\n z-index: 50;\n position: absolute;\n top: 2.25rem;\n\n &.active {\n padding: 0.5rem 1rem;\n padding-top: 1rem;\n border-radius: 1rem;\n background: rgba(217, 217, 217, 0.7);\n backdrop-filter: blur(5px);\n width: max-content;\n animation: slide-down 300ms ease;\n transform-origin: top center;\n }\n\n @keyframes slide-down {\n 0% {\n transform: scaleY(0);\n }\n 100% {\n transform: scaleY(1);\n }\n }\n`;\n\nconst DropdownLink = styled.div`\n color: inherit;\n text-decoration: none;\n\n &.active {\n color: #555555;\n }\n\n &:hover {\n text-decoration: none;\n color: #096d50 !important;\n }\n`;\n\nreturn (\n <Dropdown\n onMouseEnter={() => setShowMenu(true)}\n onMouseLeave={() => setShowMenu(false)}\n >\n {href ? (\n <DropdownLink className={href === props.page && \"active\"} href={href}>\n <Link\n style={{ textDecoration: \"none\" }}\n to={linkHref({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: href },\n })}\n >\n {title}\n </Link>\n </DropdownLink>\n ) : (\n <p className={`m-0 py-2 nav-dropdown`} style={{ cursor: \"default\" }}>\n {title} ↓\n </p>\n )}\n {showMenu && links.length !== 0 && (\n <DropdownMenu className={`${showMenu && \"active\"}`}>\n <div className=\"d-flex flex-column gap-3\">\n {links.map((link) => (\n // Check if the link is external\n <DropdownLink\n className={link.href === props.page && \"active\"}\n key={`${link.title}-${link.href}`}\n >\n {link.href.startsWith(\"http://\") ||\n link.href.startsWith(\"https://\") ? (\n // External link: Render an <a> tag\n <a\n href={link.href}\n style={{ textDecoration: \"none\" }}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {link.title}\n </a>\n ) : (\n // Internal link: Render the <Link> component\n <Link\n style={{ textDecoration: \"none\" }}\n to={linkHref({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: link.href },\n })}\n >\n {link.title}\n </Link>\n )}\n </DropdownLink>\n ))}\n </div>\n </DropdownMenu>\n )}\n </Dropdown>\n);\n" }, "devhub.components.molecule.SimpleMDE": { "": "/**\n * iframe embedding a SimpleMDE component\n * https://github.com/sparksuite/simplemde-markdown-editor\n */\n\nconst data = props.data;\nconst onChange = props.onChange ?? (() => {});\nconst height = props.height ?? \"390\";\nconst className = props.className ?? \"w-100\";\nconst embeddCSS = props.embeddCSS;\n\nState.init({\n iframeHeight: height,\n message: props.data,\n});\n\nconst profilesData = Social.get(\"*/profile/name\", \"final\");\nconst followingData = Social.get(\n `${context.accountId}/graph/follow/**`,\n \"final\"\n);\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;\n\nconst queryName =\n \"thomasguntenaar_near_devhub_proposals_quebec_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 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 ${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 query = '';\nlet showAccountAutoComplete = ${showAccountAutoComplete};\nlet showProposalIdAutoComplete = ${showProposalIdAutoComplete};\n\nfunction getSuggestedAccounts(term) {\n let results = [];\n\n term = (term || \"\").replace(/\\W/g, \"\").toLowerCase();\n const limit = 5;\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\"] = { name: { _ilike: \"%\" + id + \"%\" } };\n }\n }\n await asyncFetch(\"https://near-queryapi.api.pagoda.co/v1/graphql\", {\n method: \"POST\",\n headers: { \"x-hasura-role\": \"thomasguntenaar_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 \"thomasguntenaar_near_devhub_proposals_quebec_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 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});\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 updateIframeHeight();\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 = \"https://near.social/devhub.near/widget/app?page=proposal&id=\" + 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 === 'autocompleteSelected') {\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});\n</script>\n</body>\n</html>\n`;\n\nreturn (\n <iframe\n className={className}\n style={{\n height: `${state.iframeHeight}px`,\n }}\n srcDoc={code}\n message={{\n content: props.data?.content ?? \"\",\n followingData,\n profilesData: JSON.stringify(profilesData),\n query: query,\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 }\n }}\n />\n);\n" }, "devhub.entity.proposal.Editor": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\nconst draftKey = \"PROPOSAL_EDIT\";\nhref || (href = () => {});\n\nconst { id, timestamp } = props;\n\nconst isEditPage = typeof id === \"string\";\nconst author = context.accountId;\nconst FundingDocs =\n \"https://near.social/devhub.near/widget/app?page=community&handle=developer-dao&tab=funding\";\n\nif (!author) {\n return (\n <Widget src={\"devhub.megha19.near/widget/devhub.entity.proposal.LoginScreen\"} />\n );\n}\nlet editProposalData = null;\nlet draftProposalData = null;\n\nif (isEditPage) {\n editProposalData = Near.view(\"devhub.near\", \"get_proposal\", {\n proposal_id: parseInt(id),\n });\n}\n\nconst Container = styled.div`\n input {\n font-size: 14px !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 textarea {\n font-size: 14px !important;\n }\n\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 .text-sm {\n font-size: 13px;\n }\n\n @media screen and (max-width: 768px) {\n .h6 {\n font-size: 14px !important;\n }\n\n .h5 {\n font-size: 16px !important;\n }\n\n .text-sm {\n font-size: 11px;\n }\n\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n\n .border-bottom {\n border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color);\n }\n\n .text-xs {\n font-size: 10px;\n }\n\n .flex-2 {\n flex: 2;\n }\n\n .flex-1 {\n flex: 1;\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 &:hover {\n background-color: #f4f4f4;\n }\n }\n\n .border-1 {\n border: 1px solid #e2e6ec;\n }\n .green-btn {\n background-color: #04a46e !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n\n .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\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 .input-icon {\n display: flex;\n height: 100%;\n align-items: center;\n border-right: 1px solid #dee2e6;\n padding-right: 10px;\n }\n\n /* Tooltip container */\n .custom-tooltip {\n position: relative;\n display: inline-block;\n }\n\n /* Tooltip text */\n .custom-tooltip .tooltiptext {\n visibility: hidden;\n width: 250px;\n background-color: #fff;\n color: #6c757d;\n text-align: center;\n padding: 10px;\n border-radius: 6px;\n font-size: 12px;\n border: 0.2px solid #6c757d;\n\n /* Position the tooltip text */\n position: absolute;\n z-index: 1;\n bottom: 125%;\n left: -30px;\n\n /* Fade in tooltip */\n opacity: 0;\n transition: opacity 0.3s;\n }\n\n /* Tooltip arrow */\n .custom-tooltip .tooltiptext::after {\n content: \"\";\n position: absolute;\n top: 100%;\n left: 15%;\n margin-left: -5px;\n border-width: 5px;\n border-style: solid;\n border-color: #555 transparent transparent transparent;\n }\n\n /* Show the tooltip text when you mouse over the tooltip container */\n .custom-tooltip:hover .tooltiptext {\n visibility: visible;\n opacity: 1;\n }\n\n .form-check-input:checked {\n background-color: #04a46e !important;\n border-color: #04a46e !important;\n }\n\n .gap-6 {\n gap: 2.5rem;\n }\n\n a.no-space {\n display: inline-block;\n }\n`;\n\nconst Heading = styled.div`\n font-size: 24px;\n font-weight: 700;\n\n @media screen and (max-width: 768px) {\n font-size: 18px;\n }\n`;\n\nconst tokensOptions = [\n { label: \"NEAR\", value: \"NEAR\" },\n { label: \"USDT\", value: \"USDT\" },\n {\n label: \"USDC\",\n value: \"USDC\",\n },\n {\n label: \"Other\",\n value: \"OTHER\",\n },\n];\n\nconst devdaoAccount = \"neardevdao.near\";\n\nconst [category, setCategory] = useState(null);\nconst [title, setTitle] = useState(null);\nconst [description, setDescription] = useState(null);\nconst [summary, setSummary] = useState(null);\nconst [consent, setConsent] = useState({ toc: false, coc: false });\nconst [linkedProposals, setLinkedProposals] = useState([]);\nconst [receiverAccount, setReceiverAccount] = useState(context.accountId);\nconst [requestedSponsor, setRequestedSponsor] = useState(devdaoAccount);\nconst [requestedSponsorshipAmount, setRequestedSponsorshipAmount] =\n useState(null);\nconst [requestedSponsorshipToken, setRequestedSponsorshipToken] = useState(\n tokensOptions[2]\n);\nconst [supervisor, setSupervisor] = useState(null);\nconst [allowDraft, setAllowDraft] = useState(true);\n\nconst [loading, setLoading] = useState(true);\nconst [disabledSubmitBtn, setDisabledSubmitBtn] = useState(false);\nconst [isDraftBtnOpen, setDraftBtnOpen] = useState(false);\nconst [selectedStatus, setSelectedStatus] = useState(\"draft\");\nconst [isReviewModalOpen, setReviewModal] = useState(false);\nconst [isCancelModalOpen, setCancelModal] = useState(false);\n\nconst [showProposalPage, setShowProposalPage] = useState(false); // when user creates/edit a proposal and confirm the txn, this is true\nconst [proposalId, setProposalId] = useState(null);\nconst [proposalIdsArray, setProposalIdsArray] = useState(null);\nconst [isTxnCreated, setCreateTxn] = useState(false);\nconst [oldProposalData, setOldProposalData] = useState(null);\n\nif (allowDraft) {\n draftProposalData = Storage.privateGet(draftKey);\n}\n\nconst memoizedDraftData = useMemo(\n () => ({\n id: editProposalData.id ?? null,\n snapshot: {\n name: title,\n description: description,\n category: category,\n summary: summary,\n requested_sponsorship_usd_amount: requestedSponsorshipAmount,\n requested_sponsorship_paid_in_currency: requestedSponsorshipToken.value,\n receiver_account: receiverAccount,\n supervisor: supervisor,\n requested_sponsor: requestedSponsor,\n },\n }),\n [\n title,\n summary,\n description,\n category,\n requestedSponsorshipAmount,\n requestedSponsorshipToken,\n receiverAccount,\n supervisor,\n requestedSponsor,\n ]\n);\n\nuseEffect(() => {\n if (allowDraft) {\n let data = editProposalData || JSON.parse(draftProposalData);\n let snapshot = data.snapshot;\n if (data) {\n if (timestamp) {\n snapshot =\n data.snapshot_history.find((item) => item.timestamp === timestamp) ??\n data.snapshot;\n }\n if (\n draftProposalData &&\n editProposalData &&\n editProposalData.id === JSON.parse(draftProposalData).id\n ) {\n snapshot = {\n ...editProposalData.snapshot,\n ...JSON.parse(draftProposalData).snapshot,\n };\n }\n setCategory(snapshot.category);\n setTitle(snapshot.name);\n setSummary(snapshot.summary);\n setDescription(snapshot.description);\n setReceiverAccount(snapshot.receiver_account);\n setRequestedSponsor(snapshot.requested_sponsor);\n setRequestedSponsorshipAmount(snapshot.requested_sponsorship_usd_amount);\n setSupervisor(snapshot.supervisor);\n\n const token = tokensOptions.find(\n (item) => item.value === snapshot.requested_sponsorship_paid_in_currency\n );\n setRequestedSponsorshipToken(token ?? tokensOptions[2]);\n if (isEditPage) {\n setConsent({ toc: true, coc: true });\n }\n }\n setLoading(false);\n }\n}, [editProposalData, draftProposalData, allowDraft]);\n\nuseEffect(() => {\n if (draftProposalData) {\n setAllowDraft(false);\n }\n}, [draftProposalData]);\n\nuseEffect(() => {\n if (showProposalPage) {\n return;\n }\n setDisabledSubmitBtn(\n isTxnCreated ||\n !title ||\n !description ||\n !summary ||\n !category ||\n !requestedSponsorshipAmount ||\n !receiverAccount ||\n !requestedSponsor ||\n !consent.toc ||\n !consent.coc\n );\n const handler = setTimeout(() => {\n Storage.privateSet(draftKey, JSON.stringify(memoizedDraftData));\n }, 10000);\n\n return () => clearTimeout(handler);\n}, [\n memoizedDraftData,\n draftKey,\n draftProposalData,\n consent,\n isTxnCreated,\n showProposalPage,\n]);\n\nuseEffect(() => {\n if (\n editProposalData &&\n editProposalData?.snapshot?.linked_proposals?.length > 0\n ) {\n editProposalData.snapshot.linked_proposals.map((item) => {\n useCache(\n () =>\n Near.asyncView(\"devhub.near\", \"get_proposal\", {\n proposal_id: parseInt(item),\n }).then((proposal) => {\n setLinkedProposals([\n ...linkedProposals,\n {\n label: \"# \" + proposal.id + \" : \" + proposal.snapshot.name,\n value: proposal.id,\n },\n ]);\n }),\n item + \"linked_proposals\",\n { subscribe: false }\n );\n });\n }\n}, [editProposalData]);\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\n// show proposal created after txn approval for popup wallet\nuseEffect(() => {\n if (isTxnCreated) {\n if (editProposalData) {\n setOldProposalData(editProposalData);\n if (\n editProposalData &&\n typeof editProposalData === \"object\" &&\n oldProposalData &&\n typeof oldProposalData === \"object\" &&\n JSON.stringify(editProposalData) !== JSON.stringify(oldProposalData)\n ) {\n setCreateTxn(false);\n setProposalId(editProposalData.id);\n setShowProposalPage(true);\n }\n } else {\n const proposalIds = Near.view(\n \"devhub.near\",\n \"get_all_proposal_ids\"\n );\n if (Array.isArray(proposalIds) && !proposalIdsArray) {\n setProposalIdsArray(proposalIds);\n }\n if (\n Array.isArray(proposalIds) &&\n Array.isArray(proposalIdsArray) &&\n proposalIds.length !== proposalIdsArray.length\n ) {\n setCreateTxn(false);\n setProposalId(proposalIds[proposalIds.length - 1]);\n setShowProposalPage(true);\n }\n }\n }\n});\n\nuseEffect(() => {\n if (props.transactionHashes) {\n setLoading(true);\n useCache(\n () =>\n asyncFetch(\"https://rpc.mainnet.near.org\", {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: \"dontcare\",\n method: \"tx\",\n params: [props.transactionHashes, context.accountId],\n }),\n }).then((transaction) => {\n const transaction_method_name =\n transaction?.body?.result?.transaction?.actions[0].FunctionCall\n .method_name;\n\n const is_edit_or_add_post_transaction =\n transaction_method_name == \"add_proposal\" ||\n transaction_method_name == \"edit_proposal\";\n\n if (is_edit_or_add_post_transaction) {\n setShowProposalPage(true);\n Storage.privateSet(draftKey, null);\n }\n // show the latest created proposal to user\n if (transaction_method_name == \"add_proposal\") {\n useCache(\n () =>\n Near.asyncView(\n \"devhub.near\",\n \"get_all_proposal_ids\"\n ).then((proposalIdsArray) => {\n setProposalId(\n proposalIdsArray?.[proposalIdsArray?.length - 1]\n );\n }),\n props.transactionHashes + \"proposalIds\",\n { subscribe: false }\n );\n } else {\n setProposalId(id);\n }\n setLoading(false);\n }),\n props.transactionHashes + context.accountId,\n { subscribe: false }\n );\n } else {\n if (showProposalPage) {\n setShowProposalPage(false);\n }\n }\n}, [props.transactionHashes]);\n\nconst DropdowntBtnContainer = styled.div`\n font-size: 13px;\n min-width: 150px;\n\n .custom-select {\n position: relative;\n }\n\n .select-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n border: 1px solid #ccc;\n border-radius-top: 5px;\n cursor: pointer;\n background-color: #fff;\n border-radius: 5px;\n }\n\n .no-border {\n border: none !important;\n }\n\n .options-card {\n position: absolute;\n top: 100%;\n left: 0;\n width: 200%;\n border: 1px solid #ccc;\n background-color: #fff;\n padding: 0.5rem;\n z-index: 99;\n font-size: 13px;\n border-radius:0.375rem !important;\n }\n\n .left {\n right: 0 !important;\n left: auto !important;\n }\n\n @media screen and (max-width: 768px) {\n .options-card {\n right: 0 !important;\n left: auto !important;\n }\n }\n\n .option {\n margin-block: 5px;\n padding: 10px;\n cursor: pointer;\n border-bottom: 1px solid #f0f0f0;\n transition: background-color 0.3s ease;\n border-radius: 0.375rem !important;\n }\n\n .option:hover {\n background-color: #f0f0f0; /* Custom hover effect color */\n }\n\n .option:last-child {\n border-bottom: none;\n }\n\n .selected {\n background-color: #f0f0f0;\n }\n\n .disabled {\n background-color: #f4f4f4 !important;\n cursor: not-allowed !important;\n font-weight: 500;\n color: #b3b3b3;\n }\n\n .disabled .circle {\n opacity: 0.5;\n }\n\n .circle {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n }\n\n .grey {\n background-color: #818181;\n }\n\n .green {\n background-color: #04a46e;\n }\n\n a:hover {\n text-decoration: none;\n }\n\n}\n`;\n\nconst LoadingButtonSpinner = (\n <span\n class=\"submit-proposal-draft-loading-indicator spinner-border spinner-border-sm\"\n role=\"status\"\n aria-hidden=\"true\"\n ></span>\n);\n\nconst SubmitBtn = () => {\n const btnOptions = [\n {\n iconColor: \"grey\",\n label: \"Submit Draft\",\n description:\n \"The author can still edit the proposal and build consensus before sharing it with sponsors.\",\n value: \"draft\",\n },\n {\n iconColor: \"green\",\n label: \"Ready for Review\",\n description:\n \"Start the official review process with sponsors. This will lock the editing function, but comments are still open.\",\n value: \"review\",\n },\n ];\n\n const handleOptionClick = (option) => {\n setDraftBtnOpen(false);\n setSelectedStatus(option.value);\n };\n\n const toggleDropdown = () => {\n setDraftBtnOpen(!isDraftBtnOpen);\n };\n\n const handleSubmit = () => {\n const isDraft = selectedStatus === \"draft\";\n if (isDraft) {\n onSubmit({ isDraft });\n cleanDraft();\n } else {\n setReviewModal(true);\n }\n };\n\n const selectedOption = btnOptions.find((i) => i.value === selectedStatus);\n\n return (\n <DropdowntBtnContainer>\n <div\n className=\"custom-select\"\n tabIndex=\"0\"\n onBlur={() => setDraftBtnOpen(false)}\n >\n <div\n className={\n \"select-header d-flex gap-1 align-items-center submit-draft-button \" +\n (disabledSubmitBtn && \"disabled\")\n }\n >\n <div\n onClick={() => !disabledSubmitBtn && handleSubmit()}\n className=\"p-2 d-flex gap-2 align-items-center \"\n >\n {isTxnCreated ? (\n LoadingButtonSpinner\n ) : (\n <div className={\"circle \" + selectedOption.iconColor}></div>\n )}\n <div className={`selected-option`}>{selectedOption.label}</div>\n </div>\n <div\n className=\"h-100 p-2\"\n style={{ borderLeft: \"1px solid #ccc\" }}\n onClick={!disabledSubmitBtn && toggleDropdown}\n >\n <i class={`bi bi-chevron-${isDraftBtnOpen ? \"up\" : \"down\"}`}></i>\n </div>\n </div>\n\n {isDraftBtnOpen && (\n <div className=\"options-card\">\n {btnOptions.map((option) => (\n <div\n key={option.value}\n className={`option ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n onClick={() => handleOptionClick(option)}\n >\n <div className={`d-flex gap-2 align-items-center`}>\n <div className={\"circle \" + option.iconColor}></div>\n <div className=\"fw-bold\">{option.label}</div>\n </div>\n <div className=\"text-muted text-xs\">{option.description}</div>\n </div>\n ))}\n </div>\n )}\n </div>\n </DropdowntBtnContainer>\n );\n};\n\nconst onSubmit = ({ isDraft, isCancel }) => {\n setCreateTxn(true);\n console.log(\"submitting transaction\");\n const linkedProposalsIds = linkedProposals.map((item) => item.value) ?? [];\n const body = {\n proposal_body_version: \"V0\",\n name: title,\n description: description,\n category: category,\n summary: summary,\n linked_proposals: linkedProposalsIds,\n requested_sponsorship_usd_amount: requestedSponsorshipAmount,\n requested_sponsorship_paid_in_currency: requestedSponsorshipToken.value,\n receiver_account: receiverAccount,\n supervisor: supervisor || null,\n requested_sponsor: requestedSponsor,\n timeline: isCancel\n ? {\n status: \"CANCELLED\",\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n }\n : isDraft\n ? { status: \"DRAFT\" }\n : {\n status: \"REVIEW\",\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n },\n };\n const args = { labels: [], body: body };\n if (isEditPage) {\n args[\"id\"] = editProposalData.id;\n }\n\n Near.call([\n {\n contractName: \"devhub.near\",\n methodName: isEditPage ? \"edit_proposal\" : \"add_proposal\",\n args: args,\n gas: 270000000000000,\n },\n ]);\n};\n\nfunction cleanDraft() {\n Storage.privateSet(draftKey, null);\n}\n\nif (loading) {\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.megha19.near/widget/devhub.components.molecule.Spinner\"}\n />\n </div>\n );\n}\n\nconst [collapseState, setCollapseState] = useState({});\n\nconst CollapsibleContainer = ({ title, children, noPaddingTop }) => {\n return (\n <div\n className={\n \"border-bottom py-4 \" +\n (noPaddingTop && \"pt-0 \") +\n (collapseState[title] && \" pb-0\")\n }\n >\n <div className={\"d-flex justify-content-between \"}>\n <div className=\"h5 text-muted mb-2 mb-sm-3\">{title}</div>\n <div\n className=\"d-flex d-sm-none cursor-pointer\"\n onClick={() =>\n setCollapseState((prevState) => ({\n ...prevState,\n [title]: !prevState[title],\n }))\n }\n >\n {!collapseState[title] ? (\n <i class=\"bi bi-chevron-up h4\"></i>\n ) : (\n <i class=\"bi bi-chevron-down h4\"></i>\n )}\n </div>\n </div>\n <div className={!collapseState[title] ? \"\" : \"d-none\"}>{children}</div>\n </div>\n );\n};\n\nconst CategoryDropdown = useMemo(() => {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.CategoryDropdown\"}\n props={{\n selectedValue: category,\n onChange: setCategory,\n }}\n />\n );\n}, [draftProposalData]);\n\nconst TitleComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: title,\n onBlur: (e) => {\n setTitle(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n max: 80,\n required: true,\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nconst SummaryComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: summary,\n multiline: true,\n onBlur: (e) => {\n setSummary(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n max: 500,\n required: true,\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nconst DescriptionComponent = useMemo(() => {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Compose\"}\n props={{\n data: description,\n onChange: setDescription,\n autocompleteEnabled: true,\n autoFocus: false,\n showProposalIdAutoComplete: true,\n }}\n />\n );\n}, [draftProposalData]);\n\nconst ConsentComponent = useMemo(() => {\n return (\n <div className=\"d-flex flex-column gap-2\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Checkbox\"}\n props={{\n value: \"toc\",\n label: (\n <>\n I’ve agree to{\" \"}\n <a\n href={\n \"https://docs.google.com/document/d/1nRGy7LhpLj56SjN9MseV1x-ubH8O_c6B9DOAZ9qTwMU/edit?usp=sharing\"\n }\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n DevHub’s Terms and Conditions\n </a>\n and commit to honoring it\n </>\n ),\n isChecked: consent.toc,\n onClick: (value) =>\n setConsent((prevConsent) => ({\n ...prevConsent,\n toc: value,\n })),\n }}\n />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Checkbox\"}\n props={{\n value: \"coc\",\n label: (\n <>\n I’ve read{\" \"}\n <a\n href={\n \"https://docs.google.com/document/d/1c6XV8Sj_BRKw8jnTIsjdLPPN6Al5eEStt1ZLYSuqw9U/edit\"\n }\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n DevHub’s Code of Conduct\n </a>\n and commit to honoring it\n </>\n ),\n isChecked: consent.coc,\n onClick: (value) =>\n setConsent((prevConsent) => ({\n ...prevConsent,\n coc: value,\n })),\n }}\n />\n </div>\n );\n}, [draftProposalData]);\n\nconst ProfileComponent = useMemo(() => {\n return (\n <Widget\n src=\"mob.near/widget/Profile.ShortInlineBlock\"\n props={{\n accountId: author,\n }}\n />\n );\n}, []);\n\nconst LinkedProposalsComponent = useMemo(() => {\n return (\n <div className=\"d-flex flex-column gap-1\">\n <div className=\"text-muted w-100 text-sm\">\n Link any relevant proposals (e.g. previous milestones).\n </div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.proposal.LinkedProposalsDropdown\"\n props={{\n onChange: setLinkedProposals,\n linkedProposals: linkedProposals,\n }}\n />\n </div>\n );\n}, [draftProposalData]);\n\nconst ReceiverAccountComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: receiverAccount,\n placeholder: devdaoAccount,\n onUpdate: setReceiverAccount,\n }}\n />\n );\n}, [draftProposalData]);\n\nconst AmountComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: requestedSponsorshipAmount,\n onChange: (e) => {\n setRequestedSponsorshipAmount(e.target.value);\n },\n skipPaddingGap: true,\n inputProps: {\n type: \"text\",\n prefix: \"$\",\n inputmode: \"numeric\",\n pattern: \"[0-9]*\",\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nconst CurrencyComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: tokensOptions,\n selectedValue: requestedSponsorshipToken,\n onUpdate: (v) => {\n setRequestedSponsorshipToken(v);\n },\n }}\n />\n );\n}, [draftProposalData]);\n\nconst SponsorComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: requestedSponsor,\n placeholder: \"DevDAO\",\n onUpdate: setRequestedSponsor,\n }}\n />\n );\n}, [draftProposalData]);\n\nconst SupervisorComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: supervisor,\n onUpdate: setSupervisor,\n }}\n />\n );\n}, [draftProposalData]);\n\nif (showProposalPage) {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.Proposal\"}\n props={{ id: proposalId, ...props }}\n />\n );\n} else\n return (\n <Container className=\"w-100 py-4 px-0 px-sm-2 d-flex flex-column gap-3\">\n <Heading className=\"px-2 px-sm-0\">\n {isEditPage ? \"Edit\" : \"Create\"} Proposal\n </Heading>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.ConfirmReviewModal\"}\n props={{\n isOpen: isReviewModalOpen,\n onCancelClick: () => setReviewModal(false),\n onReviewClick: () => {\n setReviewModal(false);\n cleanDraft();\n onSubmit({ isDraft: false });\n },\n }}\n />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.ConfirmCancelModal\"}\n props={{\n isOpen: isCancelModalOpen,\n onCancelClick: () => setCancelModal(false),\n onConfirmClick: () => {\n setCancelModal(false);\n onSubmit({ isCancel: true });\n },\n }}\n />\n <div className=\"card no-border rounded-0 px-2 p-lg-0 full-width-div\">\n <div className=\"container-xl py-4 d-flex flex-wrap gap-6 w-100\">\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-2 w-100 order-2 order-md-1\"\n >\n <div className=\"d-flex gap-2 w-100\">\n <div className=\"d-none d-sm-flex\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: author,\n }}\n />\n </div>\n <div className=\"d-flex flex-column gap-4 w-100\">\n <InputContainer\n heading=\"Category\"\n description={\n <>\n Select the category that best aligns with your\n contribution to the NEAR developer community. Need\n guidance? See{\" \"}\n <a\n href={FundingDocs}\n className=\"text-decoration-underline no-space\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Funding Docs\n </a>\n .\n </>\n }\n >\n {CategoryDropdown}\n </InputContainer>\n <InputContainer\n heading=\"Title\"\n description=\"Highlight the essence of your proposal in a few words. This will appear on your proposal’s detail page and the main proposal feed. Keep it short, please :)\"\n >\n {TitleComponent}\n </InputContainer>\n <InputContainer\n heading=\"Summary\"\n description=\"Explain your proposal briefly. This is your chance to make a good first impression on the community. Include what needs or goals your work will address, your solution, and the benefit for the NEAR developer community.\"\n >\n {SummaryComponent}\n </InputContainer>\n <InputContainer\n heading=\"Description\"\n description={\n <>\n Expand on your summary with any relevant details like your\n contribution timeline, key milestones, team background,\n and a clear breakdown of how the funds will be used.\n Proposals should be simple and clear (e.g. 1 month). For\n more complex projects, treat each milestone as a separate\n proposal. Need more guidance?\n <a\n href={FundingDocs}\n className=\"text-decoration-underline no-space\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n See Funding Docs\n </a>\n .\n </>\n }\n >\n {DescriptionComponent}\n </InputContainer>\n <InputContainer heading=\"Final Consent\">\n {ConsentComponent}\n </InputContainer>\n <div className=\"d-flex justify-content-between gap-2 align-items-center\">\n <div>\n {isEditPage && (\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"btn-outline-danger shadow-none border-0 btn-sm\",\n },\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 onClick: () => setCancelModal(true),\n }}\n />\n )}\n </div>\n <div className=\"d-flex gap-2\">\n <Link\n to={\n isEditPage\n ? href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"proposal\",\n id: parseInt(id),\n },\n })\n : href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"proposals\",\n },\n })\n }\n >\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"d-flex h-100 text-muted fw-bold btn-outline shadow-none border-0 btn-sm\",\n },\n label: \"Discard Changes\",\n onClick: cleanDraft,\n }}\n />\n </Link>\n <SubmitBtn />\n </div>\n </div>\n </div>\n </div>\n </div>\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-1 w-100 order-1 order-md-2\"\n >\n <CollapsibleContainer noPaddingTop={true} title=\"Author Details\">\n <div className=\"d-flex flex-column gap-3 gap-sm-4\">\n <InputContainer heading=\"Author\">\n {ProfileComponent}\n </InputContainer>\n </div>\n </CollapsibleContainer>\n <div className=\"my-2\">\n <CollapsibleContainer title=\"Link Proposals (Optional)\">\n {LinkedProposalsComponent}\n </CollapsibleContainer>\n </div>\n <div className=\"my-2\">\n <CollapsibleContainer title=\"Funding Details\">\n <div className=\"d-flex flex-column gap-3 gap-sm-4\">\n <InputContainer\n heading=\"Recipient NEAR Wallet Address\"\n description=\"Enter the address that will receive the funds. We’ll need this to send a test transaction once your proposal is approved.\"\n >\n {ReceiverAccountComponent}\n </InputContainer>\n <InputContainer\n heading={\n <div className=\"d-flex gap-2 align-items-center\">\n Recipient Verification Status\n <div className=\"custom-tooltip\">\n <i class=\"bi bi-info-circle-fill\"></i>\n <span class=\"tooltiptext\">\n To get approved and receive payments on our\n platform, you must complete KYC/KYB verification\n using Fractal, a trusted identity verification\n solution. This helps others trust transactions with\n your account. Click \"Get Verified\" to start. <br />\n <br />\n Once verified, your profile will display a badge,\n which is valid for 365 days from the date of your\n verification. You must renew your verification upon\n expiration OR if any of your personal information\n changes.\n </span>\n </div>\n </div>\n }\n description=\"\"\n >\n <div className=\"border border-1 p-3 rounded-2\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.proposal.VerificationStatus\"\n props={{\n receiverAccount: receiverAccount,\n showGetVerifiedBtn: true,\n imageSize: 30,\n }}\n />\n </div>\n </InputContainer>\n <InputContainer\n heading=\"Total Amount (USD)\"\n description={\n <>\n Enter the exact amount you are seeking. See\n <a\n href={FundingDocs}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Funding Documentation\n </a>\n for guidelines.\n </>\n }\n >\n {AmountComponent}\n </InputContainer>\n <InputContainer\n heading=\"Currency\"\n description=\"Select your preferred currency for receiving funds. Note: The exchange rate for NEAR tokens will be the closing rate at the day of the invoice.\"\n >\n {CurrencyComponent}\n </InputContainer>\n <InputContainer heading=\"Requested Sponsor\" description=\"\">\n {SponsorComponent}\n </InputContainer>\n <InputContainer\n heading=\"Supervisor (Optional)\"\n description=\"\"\n >\n {SupervisorComponent}\n </InputContainer>\n </div>\n </CollapsibleContainer>\n </div>\n </div>\n </div>\n </div>\n </Container>\n );\n" }, "devhub.entity.post.List": { "": "// This component implementation was forked from [IndexFeed], but it does not fully implement lazy loading.\n// While this component uses InfiniteScroll, it still loads the whole list of Post IDs in one view call.\n// The contract will need to be extended with pagination support, yet, even in the current state the page loads much faster.\n// [IndexFeed]: https://near.social/#/mob.near/widget/WidgetSource?src=mob.near/widget/IndexFeed\n\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nconst { draftState, onDraftStateChange } = VM.require(\n \"devhub.megha19.near/widget/devhub.entity.post.draft\"\n);\n\nif (!href) {\n return <p>Loading modules...</p>;\n}\n\nconst QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql/`;\n\nconst queryName =\n props.queryName ?? `bo_near_devhub_v38_posts_with_latest_snapshot`;\nconst totalQueryName =\n props.totalQueryName ??\n \"bo_near_devhub_v38_posts_with_latest_snapshot_aggregate\";\nconst query = `query DevhubPostsQuery($limit: Int = 100, $offset: Int = 0, $where: ${queryName}_bool_exp = {}) {\n ${queryName}(\n limit: $limit\n offset: $offset\n order_by: {ts: desc}\n where: $where\n ) {\n post_id\n }\n }\n`;\n\nconst totalQuery = `query DevhubTotalPostsQuery($where: ${queryName}_bool_exp = {}) {\n ${totalQueryName}(\n where: $where\n ) {\n aggregate {\n count\n }\n }\n }\n`;\n\nfunction fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `bo_near` },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n}\n\nfunction searchConditionChanged() {\n return (\n props.author != state.author ||\n props.term != state.term ||\n props.tag != state.tag ||\n props.recency != state.recency\n );\n}\n\nfunction updateSearchCondition() {\n State.update({\n author: props.author,\n term: props.term,\n tag: props.tag,\n recency: props.recency,\n loading: true,\n });\n}\n\nconst initialRenderLimit = props.initialRenderLimit ?? 3;\nconst addDisplayCount = props.nextLimit ?? initialRenderLimit;\n\nState.init({\n period: \"week\",\n totalItems: 0,\n displayCount: initialRenderLimit,\n});\n\nfunction getPostIds(tag, offset) {\n if (searchConditionChanged()) {\n updateSearchCondition();\n }\n let where = {};\n let authorId = props.author;\n let label = tag || props.tag;\n if (authorId) {\n where = { author_id: { _eq: authorId }, ...where };\n }\n if (props.term) {\n where = { description: { _ilike: `%${props.term}%` }, ...where };\n }\n if (label) {\n if (typeof label === \"string\") {\n // Handle a single label\n where = { labels: { _contains: label }, ...where };\n } else if (Array.isArray(label)) {\n // Handle an array of labels\n where = {\n labels: {\n _containsAny: label,\n },\n ...where,\n };\n }\n }\n if (!props.recency) {\n // show only top level posts\n where = { parent_id: { _is_null: true }, ...where };\n }\n\n // Don't show blog and devhub-test posts\n where = {\n _and: [\n {\n _not: {\n labels: { _contains: \"blog\" },\n parent_id: { _is_null: true },\n post_type: { _eq: \"Comment\" },\n },\n },\n {\n _not: {\n labels: { _contains: \"devhub-test\" },\n },\n },\n ],\n ...where,\n };\n\n if (!offset) {\n fetchGraphQL(totalQuery, \"DevhubTotalPostsQuery\", {\n where,\n }).then((result) => {\n const data = result.body.data[totalQueryName];\n State.update({\n totalItems: data.aggregate.count,\n });\n });\n }\n\n fetchGraphQL(query, \"DevhubPostsQuery\", {\n limit: 50,\n offset: offset ?? 0,\n where,\n }).then((result) => {\n if (result.status === 200) {\n if (result.body.data) {\n const data = result.body.data[queryName];\n if (offset) {\n State.update({\n postIds: state.postIds.concat(data.map((p) => p.post_id)),\n loading: false,\n });\n } else {\n State.update({\n postIds: data.map((p) => p.post_id),\n loading: false,\n });\n }\n }\n } else {\n State.update({ loading: false });\n }\n });\n}\n\nif (!state.items || searchConditionChanged()) {\n getPostIds();\n}\n\nfunction defaultRenderItem(postId, additionalProps) {\n if (!additionalProps) {\n additionalProps = {};\n }\n // It is important to have a non-zero-height element as otherwise InfiniteScroll loads too many items on initial load\n return (\n <div className=\"py-2\" style={{ minHeight: \"150px\" }}>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.post.Post\"}\n props={{\n id: postId,\n expandable: true,\n defaultExpanded: false,\n isInList: true,\n draftState,\n isPreview: false,\n onDraftStateChange,\n ...additionalProps,\n referral: postId,\n updateTagInParent: (tag) => {\n if (typeof props.updateTagInput === \"function\") {\n props.updateTagInput(tag);\n }\n getPostIds(tag);\n },\n transactionHashes: props.transactionHashes,\n }}\n />\n </div>\n );\n}\n\nconst renderItem = props.renderItem ?? defaultRenderItem;\n\nconst cachedRenderItem = (item, i) => {\n if (props.term) {\n return renderItem(item, {\n searchKeywords: [props.term],\n });\n }\n\n const key = JSON.stringify(item);\n\n if (!(key in state.cachedItems)) {\n state.cachedItems[key] = renderItem(item);\n State.update();\n }\n return state.cachedItems[key];\n};\n\nconst ONE_DAY = 60 * 60 * 24 * 1000;\nconst ONE_WEEK = 60 * 60 * 24 * 1000 * 7;\nconst ONE_MONTH = 60 * 60 * 24 * 1000 * 30;\n\nfunction getHotnessScore(post) {\n //post.id - shows the age of the post, should grow exponentially, since newer posts are more important\n //post.likes.length - linear value\n const age = Math.pow(post.id, 5);\n const comments = post.comments;\n const commentAge = comments.reduce((sum, age) => sum + Math.pow(age, 5), 0);\n const totalAge = age + commentAge;\n //use log functions to make likes score and exponentially big age score close to each other\n return Math.log10(post.likes.length) + Math.log(Math.log10(totalAge));\n}\n\nconst getPeriodText = (period) => {\n let text = \"Last 24 hours\";\n if (period === \"week\") {\n text = \"Last week\";\n }\n if (period === \"month\") {\n text = \"Last month\";\n }\n return text;\n};\n\nlet postIds = state.postIds ?? null;\n\nconst loader = (\n <div className=\"loader\" key={\"loader\"}>\n <span\n className=\"spinner-grow spinner-grow-sm me-1\"\n role=\"status\"\n aria-hidden=\"true\"\n />\n Loading ...\n </div>\n);\n\nif (postIds === null) {\n return loader;\n}\nconst initialItems = postIds;\n\nconst jInitialItems = JSON.stringify(initialItems);\nif (state.jInitialItems !== jInitialItems) {\n // const jIndex = JSON.stringify(index);\n // if (jIndex !== state.jIndex) {\n State.update({\n jIndex,\n jInitialItems,\n items: initialItems,\n cachedItems: {},\n });\n}\n\nconst makeMoreItems = () => {\n State.update({\n displayCount: state.displayCount + addDisplayCount,\n });\n if (\n state.items.length - state.displayCount < addDisplayCount * 5 &&\n !state.loading\n ) {\n State.update({ loading: true });\n getPostIds(null, state.items.length);\n }\n};\n\nconst items = state.items ? state.items.slice(0, state.displayCount) : [];\nconst renderedItems = items.map(cachedRenderItem);\n\nconst Head =\n props.recency == \"hot\" ? (\n <div class=\"row\">\n <div class=\"fs-5 col-6 align-self-center\">\n <i class=\"bi-fire\"></i>\n <span>Hottest Posts</span>\n </div>\n <div class=\"col-6 dropdown d-flex justify-content-end\">\n <a\n class=\"btn btn-secondary dropdown-toggle\"\n href=\"#\"\n role=\"button\"\n id=\"dropdownMenuLink\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n >\n {getPeriodText(state.period)}\n </a>\n\n <ul class=\"dropdown-menu\" aria-labelledby=\"dropdownMenuLink\">\n <li>\n <button\n class=\"dropdown-item\"\n onClick={() => {\n State.update({ period: \"day\" });\n }}\n >\n {getPeriodText(\"day\")}\n </button>\n </li>\n <li>\n <button\n class=\"dropdown-item\"\n onClick={() => {\n State.update({ period: \"week\" });\n }}\n >\n {getPeriodText(\"week\")}\n </button>\n </li>\n <li>\n <button\n class=\"dropdown-item\"\n onClick={() => {\n State.update({ period: \"month\" });\n }}\n >\n {getPeriodText(\"month\")}\n </button>\n </li>\n </ul>\n </div>\n </div>\n ) : (\n <></>\n );\n\nreturn (\n <>\n {Head}\n {state.loading ? loader : null}\n {is_edit_or_add_post_transaction ? (\n <p class=\"text-secondary mt-4\">\n Post {transaction_method_name == \"edit_post\" ? \"edited\" : \"added\"}{\" \"}\n successfully. Back to{\" \"}\n <Link\n style={{\n color: \"#3252A6\",\n }}\n className=\"fw-bold\"\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"feed\" },\n })}\n >\n feed\n </Link>\n </p>\n ) : state.items.length > 0 ? (\n <div style={{ overflow: \"auto\", height: \"60vh\" }}>\n <InfiniteScroll\n pageStart={0}\n loadMore={makeMoreItems}\n hasMore={state.totalItems > state.items.length}\n loader={loader}\n useWindow={false}\n >\n {renderedItems}\n </InfiniteScroll>\n </div>\n ) : (\n <p class=\"text-secondary\">\n No posts{\" \"}\n {props.term || props.tag || props.author ? \"matches search\" : \"\"}\n {props.recency === \"hot\"\n ? \" in \" + getPeriodText(state.period).toLowerCase()\n : \"\"}\n </p>\n )}\n </>\n);\n" }, "devhub.entity.proposal.History": { "": "/*\n---props---\nprops.id: number;\nprops.newTab: boolean;\nprops.timestamp: number;\nprops.referral: any;\n*/\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || {\n href: () => {},\n};\nconst { readableDate } = VM.require(\n \"devhub.megha19.near/widget/core.lib.common\"\n) || { readableDate: () => {} };\nconst proposalId = props.id ?? (props.id ? parseInt(props.id) : 0);\nconst proposal = Near.view(\"devhub.near\", \"get_proposal\", {\n proposal_id: proposalId,\n});\nif (!proposal || !proposal.snapshot_history) {\n return <div class=\"bi bi-clock-history px-2\"></div>;\n}\nconst referral = props.referral;\n\nconst currentTimestamp = props.timestamp ?? proposal.snapshot.timestamp;\nconst snapshot = proposal.snapshot;\nconst snapshotHistory = proposal.snapshot_history\n ? Array.from(proposal.snapshot_history)\n : [];\n\nsnapshotHistory.push(snapshot);\nsnapshotHistory.reverse();\n\nconst history = (\n <div class=\"btn-group\" role=\"group\">\n <a\n class=\"card-link\"\n role=\"button\"\n title=\"proposal History\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n type=\"button\"\n >\n <div class=\"bi bi-clock-history px-2\"></div>\n </a>\n <ul class=\"dropdown-menu\">\n <a\n class=\"d-flex text-muted\"\n style={{ fontSize: \"11px\", textDecoration: \"none\", cursor: \"default\" }}\n >\n <a\n style={{\n textAlign: \"center\",\n minWidth: \"250px\",\n maxWidth: \"250px\",\n }}\n >\n Edit History\n </a>\n <a style={{ marginRight: \"8px\" }}>Compare</a>\n </a>\n {snapshotHistory.map((item) => {\n if (item === undefined) return;\n return (\n <li style={{ display: \"flex\" }}>\n <div\n style={{\n minWidth: \"250px\",\n maxWidth: \"250px\",\n }}\n >\n <a\n class=\"dropdown-item\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget\",\n params: {\n page: \"proposal\",\n id: proposalId,\n timestamp: item.timestamp,\n compareTimestamp: null,\n referral,\n },\n })}\n target={props.newTab ? \"_blank\" : undefined}\n >\n {readableDate(item.timestamp / 1000000)}\n\n <Widget\n src=\"mob.near/widget/ProfileImage\"\n props={{\n accountId: item.editor_id,\n style: {\n width: \"1.25em\",\n height: \"1.25em\",\n },\n imageStyle: {\n transform: \"translateY(-12.5%)\",\n },\n }}\n />\n {proposal.author_id.substring(0, 8)}\n </a>\n </div>\n <a\n class=\"dropdown-item\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"proposal\",\n id: proposalId,\n timestamp: currentTimestamp,\n compareTimestamp: item.timestamp,\n referral,\n },\n })}\n >\n <i class=\"bi bi-file-earmark-diff\" />\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n);\n\nreturn history;\n" }, "devhub.entity.proposal.Proposal": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || {\n href: () => {},\n};\nconst { readableDate } = VM.require(\n \"devhub.megha19.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.megha19.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 proposalURL = `https://near.org/devhub.megha19.near/widget/app?page=proposal&id=${proposal.id}&timestamp=${snapshot.timestamp}`;\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 },\n },\n {\n label: \"Approved\",\n value: {\n status: TIMELINE_STATUS.APPROVED,\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 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 },\n },\n {\n label: \"Canceled\",\n value: {\n status: TIMELINE_STATUS.CANCELED,\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Payment-processing\",\n value: {\n status: TIMELINE_STATUS.PAYMENT_PROCESSING,\n kyc_verified: false,\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 const link = `https://near.org/devhub.near/widget/app?page=proposal&id=${item.id}`;\n return (\n <a href={link} target=\"_blank\" rel=\"noopener noreferrer\">\n <div className=\"d-flex gap-2\">\n <Widget\n src={\"devhub.megha19.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 <LinkProfile account={item.snapshot.name}>\n <b className=\"text-truncate\">{item.snapshot.name}</b>\n </LinkProfile>\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 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 = { labels: [], body: body, id: proposal.id };\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_timeline\",\n args: {\n id: proposal.id,\n timeline: timeline,\n },\n gas: 270000000000000,\n },\n ]);\n};\n\nconst [isReviewModalOpen, setReviewModal] = useState(false);\nconst [isCancelModalOpen, setCancelModal] = 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 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.megha19.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={\"devhub.megha19.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.megha19.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=\"near/widget/ShareButton\"\n props={{\n postType: \"post\",\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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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 mb-4\">\n DESCRIPTION\n </div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"\n props={{ text: snapshot.description }}\n />\n\n <div className=\"d-flex gap-2 align-items-center mt-4\">\n <Widget\n src=\"devhub.megha19.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.megha19.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.megha19.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.megha19.near/widget/devhub.entity.proposal.ComposeComment\"\n }\n props={{\n ...props,\n item: item,\n notifyAccountId: authorId,\n id: proposal.id,\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.megha19.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.megha19.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 </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={<div className=\"fw-bold\">Approved</div>}\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={updatedProposalStatus.value.kyc_verified}\n label=\"Sponsor verifies KYC/KYB\"\n disabled={selectedStatusIndex !== 6}\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 <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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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 <Widget\n src={\n \"devhub.megha19.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Save\",\n disabled:\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.components.molecule.Spinner": { "": "return (\n <img\n style={{ height: \"auto\", width: \"50px\" }}\n src={\"https://i.gifer.com/origin/34/34338d26023e5515f6cc8969aa027bca.gif\"}\n alt=\"loader\"\n />\n);\n" }, "devhub.feature.proposal-search.by-input": { "": "const InputContainer = styled.div`\n display: flex;\n flex-direction: row;\n position: relative;\n width: 100%;\n`;\n\nconst [search, setSearch] = useState(props.search);\nconst onSearch = props.onSearch ?? (() => {});\nconst onEnter = props.onEnter ?? (() => {});\n\nconst updateInput = (value) => setSearch(value);\n\nuseEffect(() => {\n if (search !== props.search) {\n onSearch(search);\n }\n}, [search]);\n\nconst className = props.className ?? \"\";\n\nreturn (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1 w-100\" + className,\n value: search,\n onChange: (e) => {\n updateInput(e.target.value);\n },\n onKeyDown: (e) => e.key == \"Enter\" && onEnter(),\n skipPaddingGap: true,\n placeholder: \"Search by content\",\n inputProps: {\n prefix: <i class=\"bi bi-search m-auto\"></i>,\n },\n }}\n />\n);\n" }, "devhub.entity.team.Configurator": { "": "const { Tile } =\n VM.require(\"devhub.megha19.near/widget/devhub.components.molecule.Tile\") ||\n (() => <></>);\n\nif (!Tile) {\n return <div>Loading...</div>;\n}\n\nconst { data, onSubmit, onCancel } = props;\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n`;\n\nconst Item = styled.div`\n padding: 10px;\n margin: 5px;\n display: flex;\n align-items: center;\n flex-direction: row;\n gap: 10px;\n`;\n\nconst backwardsCompatibleLabel = (oldLabel) => {\n if (typeof oldLabel === \"string\")\n return oldLabel.startsWith(\"starts-with:\") ? oldLabel.slice(12) : oldLabel;\n else return \"\";\n};\nconst backwardsCompatibleTeam = (oldTeam) => {\n if (typeof oldTeam === \"string\")\n return oldTeam.startsWith(\"team:\") ? oldTeam.slice(5) : oldTeam;\n else return \"\";\n};\n\nconst initialData = data.members || [];\nconst [newItem, setNewItem] = useState(\"\");\nconst [teamName, setTeamName] = useState(\n backwardsCompatibleTeam(data.teamName) || \"\"\n);\nconst [label, setLabel] = useState(data.label || \"\");\nconst [labelType, setLabelType] = useState(\n (data.label || \"\").startsWith(\"starts-with:\") ? \"starts-with:\" : \"\"\n);\nconst [editPost, setEditPost] = useState(data.editPost || false);\nconst [useLabels, setUseLabels] = useState(data.useLabels || false);\nconst [members, setMembers] = useState(initialData || []);\n\nconst [showPreview, setShowPreview] = useState(data.showPreview || []);\n\nconst [warning, setWarning] = useState(\"\");\n\nconst handleAddItem = () => {\n if (newItem) {\n setMembers([...members, newItem]);\n setNewItem(\"\");\n }\n};\n\nconst handleDeleteItem = (index) => {\n const updatedData = [...members];\n updatedData.splice(index, 1);\n setMembers(updatedData);\n};\n\nconst handleSubmit = () => {\n if (newItem !== \"\") {\n return setWarning(\n \"Don't forget to add the last member or clear the field to get rid of this warning.\"\n );\n }\n if (teamName && teamName.startsWith(\"team:\")) {\n return setWarning(\"The team name can't start with 'team:'\");\n }\n if (\n !backwardsCompatibleLabel(label) ||\n !label.trim() ||\n label === \"starts-with:\"\n ) {\n return setWarning(\"Invalid label, make sure it's not taken\");\n }\n if (members.length < 1) {\n return setWarning(\"Add at least one member to the team\");\n }\n\n onSubmit({\n teamName,\n label: labelType + backwardsCompatibleLabel(label),\n editPost,\n useLabels,\n members: members.map((member) => member.trim()),\n });\n};\n\nreturn (\n <Tile className=\"p-3\">\n <Container>\n <h3>{data.teamName == \"\" ? \"Edit label\" : \"Create label\"}</h3>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.atom.Alert\"\n props={{\n onClose: () => setWarning(\"\"),\n message: warning,\n }}\n />\n {/* Moderators is only editable through the CLI except for the members property */}\n {teamName !== \"moderators\" && (\n <>\n <div className=\"flex-grow-1\">\n <span>Group name</span>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n skipPaddingGap: true,\n onChange: (e) => setTeamName(e.target.value),\n value: teamName,\n placeholder: \"Team name\",\n }}\n />\n </div>\n\n <div className=\"flex-grow-1\">\n <div>\n Would you like this group to limit their restrictions to a single\n label, or would you prefer them to restrict it with any label that\n follows a similar convention?\n </div>\n <div className=\"col-lg-6 mb-2\">\n <select\n onChange={(event) => setLabelType(event.target.value)}\n class=\"form-select\"\n aria-label=\"Select type\"\n value={labelType}\n >\n <option value=\"starts-with:\">\n Restrict multiple labels with a common prefix\n </option>\n <option value=\"\">Restrict a single label</option>\n </select>\n <div>What would you like the restricted label to be?</div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n onChange: (e) => setLabel(e.target.value),\n value: backwardsCompatibleLabel(label),\n skipPaddingGap: true,\n placeholder: \"label\",\n inputProps: {\n prefix: labelType,\n },\n }}\n />\n <div>Select label permissions</div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.team.LabelPermissions\"\n props={{\n identifier: data.teamName,\n editPost,\n setEditPost,\n useLabels,\n setUseLabels,\n disabled: false,\n }}\n />\n </div>\n </div>\n </>\n )}\n {members.map((item, index) => (\n <Item key={index}>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: item,\n skipPaddingGap: true,\n placeholder: \"member\",\n inputProps: {\n prefix: \"member\",\n disabled: true,\n },\n }}\n />\n </div>\n <button\n className=\"btn btn-outline-danger\"\n onClick={() => handleDeleteItem(index)}\n >\n <i className=\"bi bi-trash-fill\" />\n </button>\n </Item>\n ))}\n <Item>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n skipPaddingGap: true,\n onChange: (e) => setNewItem(e.target.value),\n value: newItem,\n placeholder: \"member\",\n inputProps: {\n prefix: \"member\",\n },\n }}\n />\n </div>\n <button\n className=\"btn btn-success add-member\"\n onClick={handleAddItem}\n disabled={newItem === \"\"}\n >\n <i className=\"bi bi-plus\" />\n </button>\n </Item>\n <div\n className={\"d-flex align-items-center justify-content-end gap-3 mt-4\"}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-danger shadow-none border-0\" },\n label: \"Cancel\",\n onClick: () => onCancel(),\n }}\n />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-success\" },\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Submit\",\n onClick: handleSubmit,\n }}\n />\n </div>\n </Container>\n </Tile>\n);\n" }, "devhub.entity.addon.github.kanban_board": { "": "const { DataRequest } = VM.require(\n \"devhub.megha19.near/widget/core.lib.data-request\"\n);\nDataRequest || (DataRequest = { paginated: () => {} });\n\nconst resPerPage = 100;\n\nfunction extractOwnerAndRepo(url) {\n // Remove any leading or trailing slashes and split the URL by \"/\"\n const parts = url\n .trim()\n .replace(/^\\/+|\\/+$/g, \"\")\n .split(\"/\");\n\n // Check if the URL matches the GitHub repository format\n if (parts.length === 5 && parts[2] === \"github.com\") {\n const owner = parts[3];\n const repo = parts[4];\n return { owner, repo };\n } else {\n return null;\n }\n}\n\nconst GithubKanbanBoard = ({\n columns,\n title,\n description,\n repoURL,\n ticketState,\n dataTypesIncluded,\n metadata,\n}) => {\n State.init({\n ticketsLastPage: false,\n fetchedTicketsCount: {},\n ticketsByColumn: {},\n cachedItems: {},\n displayCount: 40,\n noTicketsFound: false,\n error: null,\n });\n\n const ticketStateFilter = ticketState ?? \"all\";\n\n function fetchTickets(columnId, labelSearchTerms, allLabelsMust) {\n const pageNumber = !state.fetchedTicketsCount[columnId]\n ? 1\n : state.fetchedTicketsCount[columnId] / resPerPage + 1;\n const { repo, owner } = extractOwnerAndRepo(repoURL);\n const type =\n dataTypesIncluded.issue && dataTypesIncluded.pullRequest\n ? \"\"\n : dataTypesIncluded.issue\n ? \"type:issue\"\n : \"type:pr\";\n const labels = allLabelsMust\n ? (labelSearchTerms ?? []).map((item) => `label:${item}`).join(\" \")\n : `label:${(labelSearchTerms ?? []).join(\",\")}`;\n const state =\n ticketStateFilter === \"all\" ? \"\" : `state:${ticketStateFilter}`;\n const q = encodeURIComponent(\n `${labels} repo:${owner}/${repo} ${state} ${type}`\n );\n const res = fetch(\n `https://api.github.com/search/issues?per_page=${resPerPage}&page=${pageNumber}&q=${q}`\n );\n\n if (res !== null) {\n if (res.status !== 200) {\n State.update({\n error:\n \"The listed users and repositories cannot be searched either because the resources do not exist or you do not have permission to view them.\",\n });\n } else {\n if (!res.body.incomplete_results) {\n State.update({\n ticketsLastPage: true,\n });\n }\n if (res.body.total_count === 0) {\n State.update({\n noTicketsFound: true,\n });\n } else {\n State.update((lastKnownState) => ({\n ...lastKnownState,\n fetchedTicketsCount: {\n ...lastKnownState.fetchedTicketsCount,\n [columnId]:\n lastKnownState.fetchedTicketsCount[columnId] ?? 0 + resPerPage,\n },\n ticketsByColumn: {\n ...lastKnownState.ticketsByColumn,\n [columnId]: [\n ...(lastKnownState?.ticketsByColumn?.[columnId] ?? []),\n ...res.body.items,\n ],\n },\n }));\n }\n }\n }\n }\n\n if (\n repoURL &&\n Object.keys(state.ticketsByColumn).length !== Object.keys(columns).length\n ) {\n Object.keys(columns).map((item) => {\n const columnId = item;\n const columnData = columns[columnId];\n fetchTickets(\n columnId,\n columnData?.labelSearchTerms,\n columnData.allLabelsMust\n );\n });\n }\n\n const renderItem = (ticket) => (\n <Widget\n src={`devhub.megha19.near/widget/devhub.entity.addon.${metadata.ticket.type}`}\n props={{ metadata: metadata.ticket, payload: ticket }}\n key={ticket.id}\n />\n );\n\n const cachedRenderItem = (item, index) => {\n const key = JSON.stringify(item);\n\n if (!(key in state.cachedItems)) {\n state.cachedItems[key] = renderItem(item, index);\n State.update();\n }\n return state.cachedItems[key];\n };\n\n const makeMoreItems = (columnId, labelSearchTerms, allLabelsMust) => {\n const addDisplayCount = 20;\n const newDisplayCount = state.displayCount + addDisplayCount;\n State.update({\n displayCount: newDisplayCount,\n });\n if (state.fetchedTicketsCount[columnId] < 2 * newDisplayCount) {\n fetchTickets(columnId, labelSearchTerms, allLabelsMust);\n }\n };\n\n return (\n <div>\n <div className=\"d-flex flex-column align-items-center gap-2 pb-4\">\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n <span>{title}</span>\n </h5>\n\n <p className=\"m-0 py-1 text-secondary text-center\">{description}</p>\n </div>\n\n <div className=\"d-flex gap-3 w-100\" style={{ overflowX: \"auto\" }}>\n {Object.keys(columns).length === 0 ? (\n <div\n className={[\n \"d-flex align-items-center justify-content-center\",\n \"w-100 text-black-50 opacity-50\",\n ].join(\" \")}\n style={{ height: 384 }}\n >\n No columns were created so far.\n </div>\n ) : null}\n {Object.values(columns ?? {})?.map((column) => {\n const tickets = state.ticketsByColumn[column.id]\n ? state.ticketsByColumn[column.id].slice(0, state.displayCount)\n : [];\n const renderedItems = tickets.map(cachedRenderItem);\n\n return (\n <div\n className=\"col-3\"\n style={{ minWidth: \"300px\" }}\n key={`column-${column.id}-view`}\n >\n <div className=\"card rounded-4\">\n <div\n style={{ height: \"75vh\", overflow: \"auto\" }}\n className={[\n \"card-body d-flex flex-column gap-3 p-2\",\n \"border border-1 rounded-4\",\n ].join(\" \")}\n id={column.id}\n >\n <span className=\"d-flex flex-column py-1\">\n <h6 className=\"card-title h6 m-0\">{column.title}</h6>\n <p class=\"text-secondary m-0\">{column.description}</p>\n </span>\n {state.error && (\n <div className=\"alert alert-danger\">\n Error: {state.error}\n </div>\n )}\n {state.noTicketsFound && <p>No tickets found</p>}\n {state.fetchedTicketsCount[column.id] > 0 && (\n <InfiniteScroll\n loadMore={() =>\n makeMoreItems(\n column.id,\n column?.labelSearchTerms,\n column.allLabelsMust\n )\n }\n hasMore={!state.ticketsLastPage}\n loader={<>Loading...</>}\n useWindow={false}\n threshold={80}\n >\n <div class=\"d-flex flex-column gap-2\">\n {renderedItems}\n </div>\n </InfiniteScroll>\n )}\n </div>\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n};\n\nreturn GithubKanbanBoard(props);\n" }, "devhub.components.island.participate": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nhref || (href = () => {});\n\nconst Container = styled.div`\n margin-top: 2.25rem;\n\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n width: 100%;\n\n @media screen and (max-width: 768px) {\n flex-direction: column;\n margin-top: 0;\n }\n`;\n\nconst LinkItem = styled.a`\n color: #00ec97;\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n\n display: flex;\n align-items: center;\n\n &:hover {\n text-decoration: none;\n color: #096d50;\n }\n`;\n\nconst Links = [\n {\n links: [\n {\n title: \"Ideate on DevHub\",\n href: \"/devhub.near/widget/app?page=blog&id=2029\",\n count: 1,\n },\n {\n title: \"Post a Proposal\",\n href: \"/devhub.near/widget/app?page=blog&id=2035\",\n count: 2,\n },\n {\n title: \"Host an Event\",\n href: \"/devhub.near/widget/app?page=community&handle=hacks&tab=wiki-202\",\n count: 3,\n },\n ],\n },\n {\n links: [\n {\n title: \"Improve NEAR Docs\",\n href: \"https://github.com/near/docs\",\n count: 4,\n },\n {\n title: \"Join the Fellowship\",\n href: \"/devhub.near/widget/app?page=community&handle=fellowship&tab=wiki-201\",\n count: 5,\n },\n {\n title: \"Join NEAR Campus\",\n href: \"/devhub.near/widget/app?page=community&handle=near-campus\",\n count: 6,\n },\n ],\n },\n {\n links: [\n {\n title: \"Dive into Hackbox\",\n href: \"/hackbox.near/widget/home\",\n count: 7,\n },\n ],\n },\n];\n\nconst CTA = styled.a`\n display: flex;\n padding: 0.875rem 1rem;\n align-items: center;\n gap: 0.5rem;\n\n border-radius: 1rem;\n background: #00ec97;\n\n color: #f4f4f4 !important;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.03rem;\n\n width: max-content;\n margin-top: 1.5rem;\n\n &:hover {\n background: #555555;\n text-decoration: none !important;\n }\n\n @media screen and (max-width: 768px) {\n color: #f4f4f4 !important;\n font-size: 20px;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 24px */\n\n display: flex;\n padding: 14px 16px;\n align-items: center;\n gap: 8px;\n\n border-radius: 16px;\n background: #555555;\n\n &:hover {\n //background: #555555;\n text-decoration: none;\n }\n }\n`;\n\nconst SectionPadding = styled.div`\n padding: 3rem;\n padding-top: 0;\n\n display: flex;\n flex-direction: column;\n\n @media screen and (max-width: 768px) {\n padding: 1rem;\n padding-top: 0;\n }\n`;\n\nconst LinksContainer = styled.div`\n width: 30%;\n\n @media screen and (max-width: 768px) {\n width: 100%;\n margin-bottom: 1rem;\n }\n`;\n\nconst Content = (\n <SectionPadding>\n <Container>\n {Links.map((it) => (\n <LinksContainer key={Math.random()}>\n <div className=\"d-flex flex-column gap-3 gap-md-2\">\n {it.links.map((link) => (\n <div className=\"d-flex flex-row\">\n <span\n style={{\n color: \"#555555\",\n border: \"2px #555555 solid\",\n fontSize: 12,\n padding: 4,\n width: 22,\n height: 22,\n }}\n className=\"rounded-circle d-flex align-items-center justify-content-center me-1\"\n >\n {link.count}\n </span>{\" \"}\n <LinkItem href={link.href} target=\"no_blank\">\n {link.title}\n </LinkItem>\n </div>\n ))}\n </div>\n </LinksContainer>\n ))}\n </Container>\n\n <Link\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"contribute\" },\n })}\n style={{ textDecoration: \"none\" }}\n >\n <CTA>Learn more →</CTA>\n </Link>\n </SectionPadding>\n);\n\nreturn (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.island.home-section\"\n props={{\n title: \"/participate\",\n titleColor: \"#555555\",\n description:\n \"There are many ways to start your contribution journey. You can:\",\n children: Content,\n }}\n />\n);\n" }, "devhub.entity.community.Announcements": { "": "const { handle } = props;\nconst { getCommunity, setCommunitySocialDB } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\ngetCommunity = getCommunity || (() => <></>);\nsetCommunitySocialDB = setCommunitySocialDB || (() => <></>);\n\nconst communityData = getCommunity({ handle });\nconst [postsExists, setPostExists] = useState(false);\nconst [newUnseenPosts, setNewUnseenPosts] = useState([]);\nconst [lastQueryRequestTimestamp, setLastQueryRequestTimestamp] = useState(\n new Date().getTime()\n);\nconst [submittedAnnouncementData, setSubmittedAnnouncementData] =\n useState(null);\nconst communityAccountId = `${handle}.community.devhub.near`;\n\nlet checkIndexerInterval;\nconst onNewUnseenPosts = (newUnseenPosts) => {\n if (newUnseenPosts.length > 0) {\n clearInterval(checkIndexerInterval);\n }\n};\n\nuseEffect(() => {\n if (submittedAnnouncementData) {\n const checkForAnnouncementInSocialDB = () => {\n Near.asyncView(\"social.near\", \"get\", {\n keys: [`${communityAccountId}/post/**`],\n }).then((result) => {\n try {\n const submittedAnnouncementText = JSON.parse(\n submittedAnnouncementData.post.main\n ).text;\n const lastAnnouncementTextFromSocialDB = JSON.parse(\n result[communityAccountId].post.main\n ).text;\n if (submittedAnnouncementText === lastAnnouncementTextFromSocialDB) {\n setSubmittedAnnouncementData(null);\n checkIndexerInterval = setInterval(() => {\n setLastQueryRequestTimestamp(new Date().getTime());\n }, 500);\n return;\n }\n } catch (e) {}\n setTimeout(() => checkForAnnouncementInSocialDB(), 1000);\n });\n };\n checkForAnnouncementInSocialDB();\n }\n}, [submittedAnnouncementData]);\n\nconst MainContent = styled.div`\n padding-left: 2rem;\n flex: 3;\n @media screen and (max-width: 960px) {\n padding-left: 0rem;\n }\n .post:hover {\n background-color: inherit !important;\n }\n`;\n\nconst SidebarContainer = styled.div`\n flex: 1;\n`;\n\nconst Heading = styled.div`\n font-size: 19px;\n font-weight: 600;\n`;\n\nconst SubHeading = styled.div`\n font-size: 15px;\n font-weight: 600;\n`;\n\nconst Container = styled.div`\n flex-wrap: no-wrap;\n max-width: 100%;\n\n .max-width-100 {\n max-width: 100%;\n }\n @media screen and (max-width: 960px) {\n flex-wrap: wrap;\n }\n\n .card {\n border-radius: 1rem !important;\n }\n\n .display-none {\n display: none;\n }\n`;\n\nconst Tag = styled.div`\n border-top-right-radius: 50px;\n border-bottom-right-radius: 50px;\n border-top-left-radius: 50px;\n border-bottom-left-radius: 50px;\n padding-inline: 0.8rem;\n padding-block: 0.3rem;\n display: flex;\n gap: 0.5rem;\n border-width: 1px;\n border-style: solid;\n font-size: 14px;\n color: rgba(0, 236, 151, 1);\n font-weight: 800;\n`;\n\nconst [sort, setSort] = useState(\"desc\");\n\nreturn (\n <div className=\"w-100\" style={{ maxWidth: \"100%\" }}>\n <Container className=\"d-flex gap-3 m-3 pl-2\">\n <MainContent className=\"max-width-100\">\n <div className=\"d-flex flex-column gap-4\">\n {context.accountId &&\n (communityData?.admins ?? []).includes(context.accountId) && (\n <div className=\"card p-4\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.community.Compose\"}\n props={{\n onSubmit: (v) => {\n setSubmittedAnnouncementData(v);\n setCommunitySocialDB({ handle, data: v });\n },\n profileAccountId: `${handle}.community.devhub.near`,\n isFinished: () => submittedAnnouncementData === null,\n }}\n />\n </div>\n )}\n <div className=\"d-flex flex-wrap justify-content-between\">\n <Heading>Announcements</Heading>\n <div\n className={\n postsExists\n ? \"d-flex align-items-center gap-2\"\n : \" display-none\"\n }\n >\n <select\n name=\"sort\"\n id=\"sort\"\n class=\"form-select\"\n value={sort}\n onChange={(e) => {\n setSort(e.target.value);\n }}\n >\n <option selected value=\"desc\">\n Latest\n </option>\n <option value=\"recentcommentdesc\">Last Commented</option>\n </select>\n </div>\n </div>\n {!postsExists && (\n <div>\n <h6>No announcements exists.</h6>\n </div>\n )}\n <div className={postsExists && \"card p-4\"}>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.organism.Feed\"\n props={{\n filteredAccountIds: [communityAccountId],\n sort: sort,\n setPostExists: setPostExists,\n showFlagAccountFeature: true,\n lastQueryRequestTimestamp,\n onNewUnseenPosts,\n }}\n />\n </div>\n </div>\n </MainContent>\n <SidebarContainer>\n <div className=\"d-flex flex-column gap-3\">\n <div className=\"card p-4\">\n <div className=\"mb-2\">{communityData?.description}</div>\n <div className=\"d-flex gap-2 flex-wrap\">\n <Tag>{communityData?.tag} </Tag>\n </div>\n </div>\n <div className=\"card p-4 d-flex flex-column gap-2\">\n <SubHeading>Community Admins</SubHeading>\n {(communityData?.admins ?? []).map((accountId) => (\n <div\n key={accountId}\n className=\"d-flex\"\n style={{ fontWeight: 500 }}\n >\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.ProfileCard\"\n props={{ accountId }}\n />\n </div>\n ))}\n </div>\n </div>\n </SidebarContainer>\n </Container>\n </div>\n);\n" }, "devhub.entity.proposal.LikeButton": { "": "const item = props.item;\nconst proposalId = props.proposalId;\n\nif (!item) {\n return \"\";\n}\n\nconst likes = Social.index(\"like\", item);\n\nconst dataLoading = likes === null;\n\nconst likesByUsers = {};\n\n(likes || []).forEach((like) => {\n if (like.value.type === \"like\") {\n likesByUsers[like.accountId] = like;\n } else if (like.value.type === \"unlike\") {\n delete likesByUsers[like.accountId];\n }\n});\nif (state.hasLike === true) {\n likesByUsers[context.accountId] = {\n accountId: context.accountId,\n };\n} else if (state.hasLike === false) {\n delete likesByUsers[context.accountId];\n}\n\nconst accountsWithLikes = Object.keys(likesByUsers);\nconst hasLike = context.accountId && !!likesByUsers[context.accountId];\nconst hasLikeOptimistic =\n state.hasLikeOptimistic === undefined ? hasLike : state.hasLikeOptimistic;\nconst totalLikes =\n accountsWithLikes.length +\n (hasLike === false && state.hasLikeOptimistic === true ? 1 : 0) -\n (hasLike === true && state.hasLikeOptimistic === false ? 1 : 0);\n\nconst LikeButton = styled.button`\n border: 0;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n color: #687076;\n font-weight: 400;\n font-size: 14px;\n line-height: 17px;\n cursor: pointer;\n background: none;\n padding: 6px;\n transition: color 200ms;\n\n i {\n font-size: 16px;\n transition: color 200ms;\n\n &.bi-heart-fill {\n color: #e5484d !important;\n }\n }\n\n &:hover,\n &:focus {\n outline: none;\n color: #11181c;\n }\n`;\n\nconst likeClick = (e) => {\n e.preventDefault();\n e.stopPropagation();\n if (state.loading) {\n return;\n }\n\n State.update({\n loading: true,\n hasLikeOptimistic: !hasLike,\n });\n\n const data = {\n index: {\n like: JSON.stringify({\n key: item,\n value: {\n type: hasLike ? \"unlike\" : \"like\",\n },\n }),\n },\n };\n\n if (\n !hasLike &&\n props.notifyAccountId &&\n props.notifyAccountId !== context.accountId\n ) {\n if (proposalId) {\n data.index.notify = JSON.stringify({\n key: props.notifyAccountId,\n value: {\n type: \"devhub/like\",\n item,\n proposal: proposalId,\n },\n });\n } else {\n data.index.notify = JSON.stringify({\n key: props.notifyAccountId,\n value: {\n type: \"like\",\n item,\n },\n });\n }\n }\n Social.set(data, {\n onCommit: () => State.update({ loading: false, hasLike: !hasLike }),\n onCancel: () =>\n State.update({\n loading: false,\n hasLikeOptimistic: !state.hasLikeOptimistic,\n }),\n });\n};\n\nconst title = hasLike ? \"Unlike\" : \"Like\";\n\nreturn (\n <LikeButton\n disabled={state.loading || dataLoading || !context.accountId}\n title={title}\n onClick={likeClick}\n >\n <i className={`${hasLikeOptimistic ? \"bi-heart-fill\" : \"bi-heart\"}`} />\n {Object.values(likesByUsers ?? {}).length > 0 ? (\n <span className={`count ${hasLike ? \"liked\" : \"\"}`}>\n <Widget\n loading={likeCount || \"\"}\n src=\"mob.near/widget/N.Overlay.Faces\"\n props={{ accounts: likesByUsers, limit: 10 }}\n />\n </span>\n ) : (\n \"0\"\n )}\n </LikeButton>\n);\n" }, "devhub.components.molecule.DropDownWithSearch": { "": "const {\n selectedValue,\n onChange,\n options,\n defaultLabel,\n showSearch,\n searchInputPlaceholder,\n searchByLabel,\n searchByValue,\n onSearch,\n} = props;\n\nconst [searchTerm, setSearchTerm] = useState(\"\");\nconst [filteredOptions, setFilteredOptions] = useState(options);\nconst [isOpen, setIsOpen] = useState(false);\nconst [selectedOption, setSelectedOption] = useState({\n label:\n options?.find((item) => item.value === selectedValue)?.label ??\n defaultLabel,\n value: defaultLabel,\n});\n\nuseEffect(() => {\n if (selectedOption.value !== selectedValue) {\n setSelectedOption({\n label:\n options?.find((item) => item.value === selectedValue)?.label ??\n defaultLabel,\n value: defaultLabel,\n });\n }\n}, [selectedValue]);\n\nuseEffect(() => {\n setFilteredOptions(options);\n}, [options]);\n\nconst handleSearch = (event) => {\n const term = event.target.value.toLowerCase();\n setSearchTerm(term);\n if (typeof onSearch === \"function\") {\n onSearch(term);\n return;\n }\n\n const filteredOptions = options.filter((option) => {\n if (searchByLabel) {\n return option.label.toLowerCase().includes(term);\n }\n if (searchByValue) {\n return option.value.toString().toLowerCase().includes(term);\n }\n });\n\n setFilteredOptions(filteredOptions);\n};\n\nconst toggleDropdown = () => {\n setIsOpen(!isOpen);\n};\n\nconst handleOptionClick = (option) => {\n setSelectedOption(option);\n setIsOpen(false);\n onChange(option);\n};\n\nconst Container = styled.div`\n .drop-btn {\n width: 100%;\n text-align: left;\n padding-inline: 10px;\n }\n\n .dropdown-toggle:after {\n position: absolute;\n top: 46%;\n right: 5%;\n }\n\n .dropdown-menu {\n width: 100%;\n }\n\n .dropdown-item.active,\n .dropdown-item:active {\n background-color: #f0f0f0 !important;\n color: black;\n }\n\n .custom-select {\n position: relative;\n }\n\n .scroll-box {\n max-height: 200px;\n overflow-y: scroll;\n }\n\n .selected {\n background-color: #f0f0f0;\n }\n\n input {\n background-color: #f8f9fa;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n`;\nlet searchFocused = false;\nreturn (\n <Container>\n <div\n className=\"custom-select\"\n tabIndex=\"0\"\n onBlur={() => {\n setTimeout(() => {\n setIsOpen(searchFocused || false);\n }, 0);\n }}\n >\n <div className=\"dropdown-toggle bg-white border rounded-2 btn drop-btn\">\n <div\n className={`selected-option w-100 text-wrap ${\n selectedOption.label === defaultLabel ? \"text-muted\" : \"\"\n }`}\n onClick={toggleDropdown}\n >\n {selectedOption.label}\n </div>\n </div>\n\n {isOpen && (\n <div className=\"dropdown-menu dropdown-menu-end dropdown-menu-lg-start px-2 shadow show\">\n {showSearch && (\n <input\n type=\"text\"\n className=\"form-control mb-2\"\n placeholder={searchInputPlaceholder ?? \"Search options\"}\n value={searchTerm}\n onChange={handleSearch}\n onFocus={() => {\n searchFocused = true;\n }}\n onBlur={() => {\n setTimeout(() => {\n searchFocused = false;\n }, 0);\n }}\n />\n )}\n <div className=\"scroll-box\">\n {filteredOptions.map((option) => (\n <div\n key={option.value}\n className={`dropdown-item cursor-pointer w-100 text-wrap ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n onClick={() => handleOptionClick(option)}\n >\n {option.label}\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n </Container>\n);\n" }, "devhub.page.contribute": { "": "const Header = styled.div`\n //background: white;\n padding: 1rem 3.125rem;\n width: 100%;\n margin: 24px;\n\n @media screen and (max-width: 768px) {\n margin: 1rem 0;\n padding: 1rem;\n }\n`;\n\nconst PageHeader = styled.h1`\n color: #555555;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 500;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.24px;\n margin: 0;\n`;\n\nconst Lead = styled.h5`\n color: #151515;\n font-size: 2.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 48px */\n margin: 2.25rem 8rem;\n margin-top: 0;\n\n @media screen and (max-width: 768px) {\n font-size: 1.75rem;\n margin: 1rem;\n margin-top: 0;\n }\n`;\n\nconst Container = styled.div`\n padding: 2.25rem 8rem;\n width: 100%;\n //background-color: white;\n\n @media screen and (max-width: 768px) {\n padding: 1rem;\n }\n`;\n\nconst actions = [\n {\n title: \"Ideate on DevHub\",\n description:\n \"The first step in any NEAR ecosystem project is ideation. It is crucial to have a way to find people to share and explore ideas with, partly because it can save a lot of time based on prior discussions. But also because it can you gauge support from a diversity of stakeholders.\",\n ctaAction: \"Learn More →\",\n ctaLink: \"/devhub.near/widget/app?page=blog&id=2029\",\n },\n {\n title: \"Post a Proposal\",\n description:\n \"If you have already nurtured and refined your idea, you're ready to draft and post your funding proposal.This guide is here to help you craft a compelling, convincing, and concise proposal that will capture the interest of potential funders.\",\n ctaAction: \"Learn More →\",\n ctaLink: \"/devhub.near/widget/app?page=blog&id=2035\",\n },\n {\n title: \"Host an Event\",\n description:\n \"We are always on the lookout for events that align with our mission and provide value to the NEAR ecosystem. If you are organizing such an event, we would love to hear from you! Below is a guide on how to submit a sponsorship proposal to us.\",\n ctaAction: \"Learn More →\",\n ctaLink: \"/devhub.near/widget/app?page=community&handle=hacks&tab=wiki-202\",\n },\n {\n title: \"Improve NEAR Docs\",\n description:\n \"NEAR documentation is an open source repository that anyone can fork, extend and contribute to by creating pull requests. To get started, head over to our github repository and checkout how you can make your first contribution. \",\n ctaAction: \"Learn More →\",\n ctaLink: \"https://github.com/near/docs\",\n },\n {\n title: \"Join the Fellowship\",\n description:\n \"As the NEAR ecosystem grows rapidly, there is an increasing need to improve developer productivity. The DevDAO NEAR Platform Fellowship Program aims to solve this issue by providing guidance to new contributors from experienced developers.\",\n ctaAction: \"Learn More →\",\n ctaLink:\n \"/devhub.near/widget/app?page=community&handle=fellowship&tab=wiki-1\",\n },\n {\n title: \"Join NEAR Campus\",\n description:\n \"DevHub’s NEAR Campus supports existing student clubs, researchers, and faculties in blockchain technologies, enhancing both curricular and extracurricular activities. We aim to merge blockchain education with mainstream academics.\",\n ctaAction: \"Learn More →\",\n ctaLink: \"/devhub.near/widget/app?page=community&handle=near-campus\",\n },\n {\n title: \"Dive into Hackbox\",\n description:\n \"Hackbox is a revolutionary plug-and-play solution designed to empower local leads and community stewards in hosting hackathons easily and efficiently.\",\n ctaAction: \"Learn More →\",\n ctaLink: \"/hackbox.near/widget/home\",\n },\n];\n\nconst Card = styled.div`\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n\n h5 {\n color: #151515;\n font-size: 1.75rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 43.2px */\n\n display: flex;\n align-items: center;\n }\n\n p {\n color: #000;\n font-size: 1rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n }\n\n a {\n color: #f4f4f4;\n font-size: 1rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.48px;\n\n display: flex;\n padding: 0.5rem 1rem;\n align-items: center;\n gap: 8px;\n\n border-radius: 0.5rem;\n background: #00ec97;\n\n width: max-content;\n }\n`;\nconst ActionCard = ({ action, index }) => {\n return (\n <Card>\n <h5>\n <span\n style={{\n color: \"#151515\",\n border: \"2px #151515 solid\",\n fontSize: 12,\n padding: 4,\n width: 22,\n height: 22,\n }}\n className=\"rounded-circle d-flex align-items-center justify-content-center me-1\"\n >\n {index + 1}\n </span>\n {action.title}\n </h5>\n <p>{action.description}</p>\n <a href={action.ctaLink} target=\"no_blank\">\n {action.ctaAction}\n </a>\n </Card>\n );\n};\n\nconst ActionContainer = styled.div`\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n justify-content: center;\n column-gap: 1rem;\n row-gap: 2rem;\n\n width: 100%;\n\n @media screen and (max-width: 768px) {\n display: flex;\n flex-direction: column;\n }\n`;\n\nreturn (\n <>\n <Widget src={`devhub.megha19.near/widget/devhub.components.island.banner`} />\n <Header>\n <PageHeader>Contribute</PageHeader>\n </Header>\n <Lead>\n There are many ways to start your contribution journey. You can:\n </Lead>\n <Container>\n <ActionContainer>\n {actions.map((action, index) => (\n <ActionCard action={action} index={index} />\n ))}\n </ActionContainer>\n </Container>\n </>\n);\n" }, "devhub.page.post": { "": "const { id } = props;\n\nconst Container = styled.div`\n padding: 0 3rem 3rem 3rem;\n width: 100%;\n max-width: 100%;\n\n @media screen and (max-width: 768px) {\n padding: 0 1rem 1rem 1rem;\n }\n`;\n\nreturn (\n <Container>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.post.Post\"}\n props={{\n id,\n }}\n />\n </Container>\n);\n" }, "devhub.components.molecule.PostControls": { "": "const { className, title, icon, href, onClick } = props;\n\nconst Button = styled.button`\n display: flex;\n align-items: center;\n gap: 8px;\n\n border-radius: 4px;\n background: #04a46e;\n\n color: #f4f4f4;\n font-size: 16px;\n font-style: normal;\n font-weight: 700;\n line-height: 20px; /* 125% */\n\n padding: 0.5rem 1rem;\n\n &:hover {\n background: #555555;\n text-decoration: none !important;\n }\n\n outline: none;\n border: none;\n`;\n\nreturn (\n <div className={`d-flex flex-row-reverse ${props.className}`}>\n {props.href ? (\n <Link to={props.href} style={{ textDecoration: \"none\" }}>\n <Button\n className=\"post-control\"\n data-testid={props.testId ? props.testId : \"\"}\n >\n <i className={props.icon ? props.icon : \"bi bi-plus-circle-fill\"}></i>\n {props.title}\n </Button>\n </Link>\n ) : (\n <Button\n onClick={props.onClick}\n className=\"post-control\"\n data-testid={props.testId ? props.testId : \"\"}\n >\n <i className={props.icon ? props.icon : \"bi bi-plus-circle-fill\"}></i>\n {props.title || \"Post\"}\n </Button>\n )}\n </div>\n);\n" }, "devhub.entity.community.Provider": { "": "const { handle, Children } = props;\n\nconst {\n getAccountCommunityPermissions,\n createCommunity,\n updateCommunity,\n deleteCommunity,\n getCommunity,\n setCommunityAddons,\n} = VM.require(\"devhub.megha19.near/widget/core.adapter.devhub-contract\");\n\nif (\n !getCommunity ||\n !getAccountCommunityPermissions ||\n !createCommunity ||\n !updateCommunity ||\n !deleteCommunity ||\n !setCommunityAddons\n) {\n return <p>Loading modules...</p>;\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\nconst [isLoading, setIsLoading] = useState(false);\nconst [error, setError] = useState(null);\n\nconst community = getCommunity({ handle });\n\nconst permissions = getAccountCommunityPermissions({\n account_id: context.accountId,\n community_handle: handle,\n}) || {\n can_configure: false,\n can_delete: false,\n};\n\nif (isLoading) {\n return (\n <CenteredMessage height={\"384px\"}>\n <h2>Loading...</h2>\n </CenteredMessage>\n );\n} else if (!community) {\n return (\n <CenteredMessage height={\"384px\"}>\n <h2>{`Community with handle \"${handle}\" not found.`}</h2>\n </CenteredMessage>\n );\n}\n\nfunction handleUpdateCommunity(v) {\n updateCommunity(v);\n}\n\nreturn (\n <Children\n permissions={permissions}\n community={community}\n setCommunityAddons={setCommunityAddons}\n createCommunity={createCommunity}\n updateCommunity={handleUpdateCommunity}\n deleteCommunity={deleteCommunity}\n />\n);\n" }, "devhub.entity.proposal.CommentsAndLogs": { "": "const snapshotHistory = props.snapshotHistory;\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});\n\nfunction sortTimelineAndComments() {\n const comments = Social.index(\"comment\", props.item);\n\n if (state.changedKeysListWithValues === null) {\n const changedKeysListWithValues = snapshotHistory\n .slice(1)\n .map((item, index) => {\n const startingPoint = snapshotHistory[index]; // Set comparison to the previous item\n return {\n editorId: item.editor_id,\n ...getDifferentKeysWithValues(startingPoint, item),\n };\n });\n State.update({ changedKeysListWithValues });\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://near.social/devhub.near/widget/app?page=proposal&id=${props.id}&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.megha19.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: accountId,\n }}\n />\n </div>\n <CommentContainer\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={\n \"devhub.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"\n }\n props={{\n text: content.text,\n }}\n />\n\n <div className=\"d-flex gap-2 align-items-center mt-4\">\n <Widget\n src=\"devhub.megha19.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.megha19.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: oldValue,\n }}\n />\n to{\" \"}\n <Widget\n src={\"devhub.megha19.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 return !oldValue && newValue && <span>completed review</span>;\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 // 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.megha19.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 \"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\") {\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 className=\"inline-flex fw-bold text-black\">\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.page.admin.restrictedLabelsTab": { "": "const { accessControlInfo, createEditTeam, teamNames } = props;\n\nconst [createTeam, setCreateTeam] = useState(false);\n\nreturn (\n <>\n <h1>Restricted Labels</h1>\n <h5>\n Create special labels and control who can use and edit posts with those\n labels.\n </h5>\n {!createTeam ? (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.PostControls\"}\n props={{\n onClick: () => setCreateTeam(true),\n title: \"Create label\",\n testId: \"create-team\",\n }}\n />\n ) : (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.team.Configurator\"}\n props={{\n onCancel: () => setCreateTeam(false),\n onSubmit: (params) =>\n createEditTeam({ ...params, contractCall: \"add_member\" }),\n }}\n />\n )}\n\n <div class=\"table-responsive mt-3\">\n <table class=\"table table-sm table-bordered table-striped\">\n <thead class=\"thead-dark\">\n <tr>\n <th scope=\"col\">label name</th>\n <th scope=\"col\">Type</th>\n <th scope=\"col\">Members</th>\n <th scope=\"col\">Only allow members to use label</th>\n <th scope=\"col\">Allow members to edit any post with label</th>\n <th scope=\"col\">Actions</th>\n </tr>\n </thead>\n\n <tbody>\n {(teamNames || [])\n .filter(\n (teamName) =>\n teamName !== \"team:moderators\" && teamName.startsWith(\"team:\")\n )\n .sort()\n .map((teamName) => {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.team.LabelRow\"}\n props={{\n teamName,\n }}\n />\n );\n })}\n </tbody>\n </table>\n </div>\n </>\n);\n" }, "devhub.entity.community.configuration.AccessControlConfigurator": { "": "const CommunityAccessControlSchema = {\n admins: {\n format: \"comma-separated\",\n inputProps: { required: true },\n label: \"Admins\",\n order: 1,\n },\n};\n\nconst Struct = VM.require(\"devhub.megha19.near/widget/core.lib.struct\");\n\nif (!Struct) {\n return <p>Loading modules...</p>;\n}\n\nconst AutoComplete = styled.div`\n z-index: 5;\n\n > div > div {\n padding: calc(var(--padding) / 2);\n }\n`;\n\nconst Wrapper = styled.div`\n .container {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 0.5em;\n }\n\n .admins-item {\n display: inline-block;\n padding: 0.6em 0.8em;\n border-radius: 10px;\n border: 1px solid lightgray;\n position: relative;\n }\n\n .admins-item .remove {\n position: absolute;\n right: 5px;\n top: 0;\n font-size: 18px;\n color: grey;\n cursor: pointer;\n }\n\n .admins-input {\n flex-grow: 1;\n border: none;\n outline: none;\n }\n\n input[type=\"text\"]:disabled {\n all: inherit;\n }\n\n input::placeholder {\n font-size: 16px;\n }\n`;\n\nconst { data, onSubmit, onCancel, setIsActive, isActive } = props;\nconst initialValues = Struct.typeMatch(CommunityAccessControlSchema)\n ? Struct.pick(data ?? {}, Object.keys(CommunityAccessControlSchema))\n : {};\n\nconst [admins, setAdmins] = useState(initialValues?.admins ?? []);\nconst [text, setText] = useState(\"\");\nconst [showAccountAutocomplete, setShowAutoAutocomplete] = useState(false);\n\nfunction handleKeyDown(e) {\n if (e.key !== \"Enter\") return;\n const value = e.target.value;\n if (!value.trim()) return;\n // Add the value to the admins array\n setAdmins([...admins, value]);\n setText(\"\");\n}\n\nconst onCancelClick = () => {\n setAdmins(initialValues?.admins ?? []);\n setIsActive(false);\n};\n\nconst onSubmitClick = () => {\n onSubmit({ admins: admins.map((admin) => admin.trim()) });\n setIsActive(false);\n};\n\nfunction autoCompleteAccountId(id) {\n setAdmins([...admins, id]);\n setText(\"\");\n setShowAutoAutocomplete(false);\n}\n\nreturn (\n <Wrapper className=\"flex-grow-1 d-flex flex-column gap-4\">\n <div className=\"container\">\n {admins.map((admin, index) => (\n <div className=\"admins-item\" key={index}>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.ProfileCard\"}\n props={{\n accountId: admin,\n nearDevGovGigsWidgetsAccountId: \"devhub.megha19.near\",\n openLinkInNewTab: true,\n }}\n />\n {/* don't allow removal if only 1 admin is added */}\n {admins.length > 1 && isActive && (\n <span\n className=\"remove\"\n onClick={() => setAdmins(admins.filter((item) => item !== admin))}\n >\n &times;\n </span>\n )}\n </div>\n ))}\n <input\n disabled={!isActive}\n value={text}\n onChange={(v) => {\n setShowAutoAutocomplete(true);\n setText(v.target.value);\n }}\n onKeyDown={handleKeyDown}\n type=\"text\"\n className=\"admins-input\"\n placeholder={isActive && \"Add Admins here...\"}\n />\n </div>\n {showAccountAutocomplete && (\n <AutoComplete>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.AccountAutocomplete\"\n props={{\n term: text,\n onSelect: autoCompleteAccountId,\n onClose: () => setShowAutoAutocomplete(false),\n filterAccounts: admins,\n }}\n />\n </AutoComplete>\n )}\n {isActive && (\n <div className=\"d-flex align-items-center justify-content-end gap-3 mt-auto\">\n <Widget\n src={\"devhub.megha19.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.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-success\" },\n disabled: Struct.isEqual(admins, initialValues?.admins ?? []),\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Submit\",\n onClick: onSubmitClick,\n }}\n />\n </div>\n )}\n </Wrapper>\n);\n" }, "devhub.entity.addon.blog.editor.provider": { "": "const { getPost } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n) || { getPost: () => {} };\n\nconst { Layout, handle } = props;\n\nconst QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql/`;\n\nconst fetchGraphQL = (operationsDoc, operationName, variables) => {\n return fetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `bo_near` },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n};\n\nconst queryName =\n props.queryName ?? `bo_near_devhub_v38_posts_with_latest_snapshot`;\n\nconst query = `query DevhubPostsQuery($limit: Int = 100, $offset: Int = 0, $where: ${queryName}_bool_exp = {}) {\n ${queryName}(\n limit: $limit\n offset: $offset\n order_by: {block_height: desc}\n where: $where\n ) {\n post_id\n }\n }\n`;\n\nconst includeLabels = [\"blog\", handle];\n\nconst buildWhereClause = () => {\n let where = {};\n if (props.author) {\n where = { author_id: { _eq: props.author }, ...where };\n }\n if (props.term) {\n where = { description: { _ilike: `%${props.term}%` }, ...where };\n }\n if (includeLabels && Array.isArray(includeLabels)) {\n const labelConditions = includeLabels.map((label) => ({\n labels: { _contains: label },\n }));\n\n where = { _and: [...labelConditions, where] };\n }\n if (props.excludeLabels && Array.isArray(props.excludeLabels)) {\n const labelConditions = props.excludeLabels.map((label) => ({\n labels: { _nin: label },\n }));\n\n where = { _and: [...labelConditions, where] };\n }\n if (!props.recency) {\n where = { parent_id: { _is_null: true }, ...where };\n }\n return where;\n};\n\nconst variables = { limit: DISPLAY_COUNT, offset, where: buildWhereClause() };\n\nconst posts = fetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `bo_near` },\n body: JSON.stringify({\n query: query,\n variables: variables,\n operationName: \"DevhubPostsQuery\",\n }),\n});\n\nconst handleOnChange = (v) => {\n console.log(\"onChange\", v);\n};\n\nconst handleGetData = (v) => {\n const postId = parseInt(v);\n return Near.asyncView(\"devgovgigs.near\", \"get_post\", {\n post_id: postId,\n }).then((post) => {\n const description = JSON.parse(post.snapshot.description || \"null\") || {};\n return {\n id: postId,\n ...description,\n };\n });\n};\n\nconst handleOnSubmit = (v, isEdit) => {\n if (isEdit) {\n Near.call({\n contractName: \"devgovgigs.near\",\n methodName: \"edit_post\",\n args: {\n id: parseInt(v.id),\n labels: [\"blog\", handle],\n body: {\n post_type: \"Comment\",\n description: JSON.stringify(v),\n comment_version: \"V2\",\n },\n },\n gas: Big(10).pow(14),\n });\n } else {\n Near.call({\n contractName: \"devgovgigs.near\",\n methodName: \"add_post\",\n args: {\n labels: [\"blog\", handle],\n body: {\n post_type: \"Comment\",\n description: JSON.stringify(v),\n comment_version: \"V2\",\n },\n },\n gas: Big(10).pow(14),\n });\n }\n};\n\nconst handleOnCancel = (v) => {\n console.log(\"onCancel\", v);\n};\n\nreturn (\n <Layout\n data={posts.body.data.bo_near_devhub_v38_posts_with_latest_snapshot || []}\n getData={handleGetData}\n onChange={handleOnChange}\n onSubmit={handleOnSubmit}\n onCancel={handleOnCancel}\n />\n);\n" }, "devhub.entity.addon.wiki.Configurator": { "": "const { data, onSubmit } = props;\n\nconst initialData = data;\nconst [content, setContent] = useState(data.content || \"\");\nconst [title, setTitle] = useState(data.title || \"\");\nconst [subtitle, setSubtitle] = useState(data.subtitle || \"\");\n\nconst [textAlign, setTextAlign] = useState(data.textAlign || \"left\");\n\nconst Container = styled.div`\n width: 100%;\n margin: 0 auto;\n padding: 20px;\n text-align: left;\n`;\n\nconst FormContainer = styled.div`\n padding-top: 1rem;\n\n & > *:not(:last-child) {\n margin-bottom: 1rem;\n }\n`;\n\nconst hasDataChanged = () => {\n return (\n content !== initialData.content ||\n title !== initialData.title ||\n subtitle !== initialData.subtitle ||\n textAlign !== initialData.textAlign\n );\n};\n\nconst handleSubmit = () => {\n if (title) onSubmit({ title, subtitle, content, textAlign });\n};\n\nreturn (\n <Container>\n <ul className=\"nav nav-tabs\" id=\"editPreviewTabs\" role=\"tablist\">\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className=\"nav-link active\"\n id=\"edit-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#edit\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"edit\"\n aria-selected=\"true\"\n >\n Edit\n </button>\n </li>\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className=\"nav-link\"\n id=\"preview-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#preview\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"preview\"\n aria-selected=\"false\"\n >\n Preview\n </button>\n </li>\n </ul>\n <div className=\"tab-content\" id=\"editPreviewTabsContent\">\n <div\n className=\"tab-pane show active p-4\"\n id=\"edit\"\n role=\"tabpanel\"\n aria-labelledby=\"edit-tab\"\n style={{ position: \"relative\" }}\n >\n <div style={{ position: \"absolute\", top: 10, right: 0 }}>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Switch\"\n props={{\n currentValue: textAlign,\n key: \"textAlign\",\n onChange: (e) => setTextAlign(e.target.value),\n options: [\n { label: \"Left\", value: \"left\" },\n { label: \"Center\", value: \"center\" },\n { label: \"Right\", value: \"right\" },\n ],\n }}\n />\n </div>\n <FormContainer>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n label: \"Title\",\n className: \"flex-grow-1\",\n onChange: (e) => setTitle(e.target.value),\n value: title,\n inputProps: {\n min: 2,\n max: 60,\n required: true,\n },\n }}\n />\n </div>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n label: \"Subtitle\",\n className: \"flex-grow-1\",\n onChange: (e) => setSubtitle(e.target.value),\n value: subtitle,\n inputProps: {\n min: 2,\n max: 250,\n },\n }}\n />\n </div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.MarkdownEditor\"\n props={{ data: { content }, onChange: setContent }}\n />\n </FormContainer>\n <div\n className={\"d-flex align-items-center justify-content-end gap-3 mt-4\"}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-success\" },\n disabled: !hasDataChanged() || !title || !content,\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Publish\",\n onClick: handleSubmit,\n }}\n />\n </div>\n </div>\n <div\n className=\"tab-pane\"\n id=\"preview\"\n role=\"tabpanel\"\n aria-labelledby=\"preview-tab\"\n >\n <div className=\"w-100 h-100 p-4\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.addon.wiki.Viewer\"\n props={{ title, subtitle, content, textAlign }}\n />\n </div>\n </div>\n </div>\n </Container>\n);\n" }, "devhub.feature.proposal-search.by-stage": { "": "// The timeline is stored in jsonb. The value is used to search for as part of\n// that json so it doesn't need to be an exact match.\nconst options = [\n { label: \"Draft\", value: \"DRAFT\" },\n { label: \"Review\", value: \"REVIEW\" },\n { label: \"Approved\", value: \"APPROVED\" },\n { label: \"Approved - Conditional\", value: \"CONDITIONAL\" },\n { label: \"Rejected\", value: \"REJECTED\" },\n { label: \"Cancelled\", value: \"CANCELLED\" },\n { label: \"Payment Processing\", value: \"PAYMENT\" },\n { label: \"Funded\", value: \"FUNDED\" },\n { label: \"None\", value: \"\" },\n];\n\nconst setSelected = props.onStateChange ?? (() => {});\n\nreturn (\n <div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: options,\n label: \"Stage\",\n onUpdate: (v) => {\n setSelected(v);\n },\n }}\n />\n </div>\n);\n" }, "devhub.notification.LR": { "": "let { accountId, blockHeight, value } = props;\n\nconst showAuthorProfile =\n value.type == \"devhub/mention\" || value.type == \"devhub/edit\";\n\nif (showAuthorProfile) {\n accountId = value.notifier;\n}\n\nreturn (\n <div className=\"d-flex justify-content-between\">\n <div className=\"me-2 text-truncate\">\n <div className=\"text-truncate\">\n <Widget\n src=\"mob.near/widget/N.ProfileLine\"\n props={{ accountId, tooltip: true, link: true }}\n />\n </div>\n <div className=\"text-truncate text-muted\" style={{ paddingLeft: \"1em\" }}>\n {props.L}\n <Widget src=\"mob.near/widget/TimeAgo\" props={{ blockHeight }} />\n </div>\n </div>\n <div className=\"text-nowrap\">{props.R}</div>\n </div>\n);\n" }, "devhub.entity.community.Sidebar": { "": "const { href } = VM.require(\"devhub.megha19.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.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"}\n props={{\n text: community.bio_markdown,\n }}\n />\n <small class=\"text-muted mb-3\">\n <Link\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"feed\", tag: community.tag },\n })}\n >\n <Widget\n src={\"devhub.megha19.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.megha19.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.megha19.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.megha19.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.entity.community.configuration.ConfigurationSection": { "": "const {\n title,\n hasConfigurePermissions,\n Configurator,\n Preview,\n headerRight,\n forceEditActive,\n} = props;\n\nconst [isEditActive, setEditActive] = useState(forceEditActive || false);\n\nfunction SectionHeader() {\n return (\n <div\n className=\"d-flex align-items-center justify-content-between w-100 pb-3\"\n style={{ minHeight: 30 }}\n >\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n <span>{title}</span>\n </h5>\n {headerRight ||\n (hasConfigurePermissions && (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-sm btn-secondary\" },\n icon: {\n type: \"bootstrap_icon\",\n variant: isEditActive ? \"bi-x-circle\" : \"bi-pen-fill\",\n },\n label: isEditActive ? \"Cancel\" : \"Edit\",\n onClick: () => setEditActive(!isEditActive),\n }}\n />\n ))}\n </div>\n );\n}\n\nreturn (\n <div>\n <SectionHeader />\n <Configurator\n isActive={isEditActive}\n setIsActive={setEditActive}\n onCancel={() => setEditActive(!isEditActive)}\n />\n </div>\n);\n" }, "devhub.entity.proposal.AccountInput": { "": "const value = props.value;\nconst placeholder = props.placeholder;\nconst onUpdate = props.onUpdate;\n\nconst [account, setAccount] = useState(value);\nconst [showAccountAutocomplete, setAutoComplete] = useState(false);\nconst [isValidAccount, setValidAccount] = useState(true);\nconst AutoComplete = styled.div`\n max-width: 400px;\n margin-top: 1rem;\n`;\n\nuseEffect(() => {\n if (value !== account) {\n setAccount(value);\n }\n}, [value]);\n\nuseEffect(() => {\n if (value !== account) {\n onUpdate(account);\n }\n}, [account]);\n\nuseEffect(() => {\n const handler = setTimeout(() => {\n const valid = account.length === 64 || (account ?? \"\").includes(\".near\");\n setValidAccount(valid);\n setAutoComplete(!valid);\n }, 100);\n\n return () => {\n clearTimeout(handler);\n };\n}, [account]);\n\nreturn (\n <div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: account,\n onChange: (e) => {\n setAccount(e.target.value);\n },\n skipPaddingGap: true,\n placeholder: placeholder,\n inputProps: {\n max: 64,\n prefix: \"@\",\n },\n }}\n />\n {account && !isValidAccount && (\n <div style={{ color: \"red\" }} className=\"text-sm mt-1\">\n Please enter valid account ID\n </div>\n )}\n {showAccountAutocomplete && (\n <AutoComplete>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.AccountAutocomplete\"\n props={{\n term: account,\n onSelect: (id) => {\n setAccount(id);\n setAutoComplete(false);\n },\n onClose: () => setAutoComplete(false),\n }}\n />\n </AutoComplete>\n )}\n </div>\n);\n" }, "devhub.feature.proposal-search.by-category": { "": "const options = [\n {\n label: \"DevDAO Operations\",\n value: \"DevDAO Operations\",\n },\n {\n label: \"DevDAO Platform\",\n value: \"DevDAO Platform\",\n },\n {\n label: \"Events & Hackathons\",\n value: \"Events & Hackathons\",\n },\n {\n label: \"Engagement & Awareness\",\n value: \"Engagement & Awareness\",\n },\n {\n label: \"Decentralized DevRel\",\n value: \"Decentralized DevRel\",\n },\n {\n label: \"Universities & Bootcamps\",\n value: \"Universities & Bootcamps\",\n },\n {\n label: \"Tooling & Infrastructure\",\n value: \"Tooling & Infrastructure\",\n },\n {\n label: \"Other\",\n value: \"Other\",\n },\n {\n label: \"None\",\n value: \"\",\n },\n];\n\nconst setSelected = props.onStateChange ?? (() => {});\n\nreturn (\n <div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: options,\n label: \"Category\",\n onUpdate: (v) => {\n setSelected(v);\n },\n }}\n />\n </div>\n);\n" }, "devhub.components.molecule.Select": { "": "const SelectInput = ({\n className,\n key,\n label,\n onChange,\n options,\n placeholder,\n value,\n style,\n ...otherProps\n}) => {\n const renderedLabels =\n [(label?.length ?? 0) > 0 && <span>{label}</span>] || [];\n\n return (\n <div\n className={[\n \"d-flex flex-column flex-1 align-items-start justify-content-evenly\",\n className,\n ].join(\" \")}\n style={style}\n {...otherProps}\n >\n {renderedLabels.length > 0 && (\n <span className=\"d-flex justify-content-between align-items-center gap-3 w-100\">\n {renderedLabels}\n </span>\n )}\n\n <div className=\"input-group\">\n <select\n className=\"form-select border border-2\"\n value={value}\n onChange={onChange}\n aria-label={label}\n >\n <option value=\"\" disabled hidden>\n {placeholder}\n </option>\n {options.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n );\n};\n\nreturn SelectInput(props);\n" }, "devhub.components.layout.LikeButton.Faces": { "": "const accountId = context.accountId;\nconst likesByUsers = props.likesByUsers || {};\nconst limit = props.limit ?? 3;\n\nlet likes = Object.keys(likesByUsers).reverse();\n\nconst graphLikes = [];\nconst nonGraph = [];\n\nconst graph =\n (accountId &&\n Social.keys(`${accountId}/graph/follow/*`, \"final\")[accountId].graph\n .follow) ||\n {};\n\nlikes.forEach((accountId) => {\n if (accountId in graph) {\n graphLikes.push(accountId);\n } else {\n nonGraph.push(accountId);\n }\n});\n\nlet faces = [...graphLikes, ...nonGraph];\n\nif (faces.length < limit + 3) {\n limit = faces.length;\n}\n\nconst renderFaces = faces.splice(0, limit);\n\nconst Faces = styled.span`\n .face {\n display: inline-block;\n position: relative;\n margin: -0.1em;\n height: 1.5em;\n width: 1.5em;\n min-width: 1.5em;\n vertical-align: top;\n img {\n object-fit: cover;\n border-radius: 50%;\n width: 100%;\n height: 100%;\n }\n }\n`;\n\nconst Others = styled.span`\n &:hover {\n color: white !important;\n }\n`;\n\nconst numLikes = likes.length - limit;\n\nreturn (\n <>\n <Faces className=\"ms-1\">\n {renderFaces.map((accountId, i) => (\n <a\n key={i}\n href={`#/mob.near/widget/ProfilePage?accountId=${accountId}`}\n className=\"text-decoration-none d-inline-block\"\n >\n <Widget\n src=\"mob.near/widget/Profile.OverlayTrigger\"\n props={{\n accountId,\n children: (\n <Widget\n src=\"mob.near/widget/ProfileImage\"\n props={{\n metadata,\n accountId,\n widgetName,\n style: { zIndex: 10 - i },\n className: \"face\",\n tooltip: false,\n imageStyle: {},\n imageClassName: \"\",\n }}\n />\n ),\n }}\n />\n </a>\n ))}\n </Faces>\n {numLikes > 0 ? (\n <OverlayTrigger\n placement=\"auto\"\n overlay={\n <Tooltip>\n <div\n className=\"text-truncate text-start\"\n style={{ maxWidth: \"16em\" }}\n >\n {faces.slice(0, 10).map((accountId, i) => (\n <Fragment key={i}>\n <Widget\n src=\"mob.near/widget/ProfileLine\"\n props={{ accountId, link: false }}\n />\n <br />\n </Fragment>\n ))}\n {faces.length > 10 ? \"...\" : \"\"}\n </div>\n </Tooltip>\n }\n >\n <span className=\"ms-1\">\n and {numLikes} other{numLikes === 1 ? \"\" : \"s\"}\n </span>\n </OverlayTrigger>\n ) : (\n \"\"\n )}\n </>\n);\n" }, "devhub.components.molecule.Checkbox": { "": "const CheckBox = ({ value, isChecked, label, onClick }) => {\n const [checked, setChecked] = useState(isChecked);\n\n useEffect(() => {\n if (isChecked !== checked) {\n setChecked(isChecked);\n }\n }, [isChecked]);\n\n useEffect(() => {\n onClick(checked);\n }, [checked]);\n\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n value={value}\n checked={checked}\n onChange={(e) => setChecked(e.target.checked)}\n />\n <label class=\"form-check-label text-sm\">{label}</label>\n </div>\n );\n};\n\nreturn CheckBox(props);\n" }, "devhub.entity.community.Activity": { "": "const { handle } = props;\n\nconst { getCommunity } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\ngetCommunity = getCommunity || (() => <></>);\nhref || (href = () => {});\n\nif (!handle) {\n return <p>Handle not defined</p>;\n}\n\nif (!href) {\n return <p>Loading modules...</p>;\n}\n\n// TODO: Why do we need to get community data again? Isn't the tag the handle...\nconst communityData = getCommunity({ handle });\n\nif (communityData === null) {\n return <div>Loading...</div>;\n}\n\nconst MainContent = styled.div`\n flex-grow: 1;\n max-width: 75%;\n\n @media screen and (max-width: 960px) {\n max-width: 100%;\n }\n`;\n\nconst SidebarContainer = styled.div`\n max-width: 25%;\n margin-right: 1.5rem;\n\n @media screen and (max-width: 960px) {\n display: none;\n }\n`;\n\nreturn (\n <div style={{ maxWidth: \"100%\", width: \"100%\" }}>\n <div class=\"col\">\n <div class=\"d-flex w-100\">\n <MainContent>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.feature.post-search.panel\"}\n props={{\n hideHeader: true,\n tag: communityData.tag,\n children: (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.components.molecule.PostControls\"\n }\n props={{\n title: \"Post\",\n href: href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"create\",\n labels: [communityData.tag],\n },\n }),\n }}\n />\n ),\n recency,\n transactionHashes: props.transactionHashes,\n }}\n />\n </MainContent>\n <SidebarContainer>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.community.Sidebar\"}\n props={{ community: communityData }}\n />\n </SidebarContainer>\n </div>\n </div>\n </div>\n);\n" }, "devhub.entity.proposal.CategoryTag": { "": "const category = props.category;\nconst getClassNameByCategory = () => {\n switch (category) {\n case \"DevDAO Operations\":\n return \"purple\";\n case \"DevDAO Platform\":\n return \"yellow\";\n case \"Decentralized DevRel\":\n return \"cyan\";\n case \"Universities & Bootcamps\":\n return \"mustard\";\n case \"Engagement & Awareness\":\n return \"red\";\n case \"Events & Hackathons\":\n return \"green\";\n case \"Tooling & Infrastructure\":\n return \"pink\";\n default:\n return \"grey\";\n }\n};\n\nconst Container = styled.div`\n @media screen and (max-width: 768px) {\n font-size: 11px;\n }\n font-size: 13px;\n .tag {\n color: white;\n padding-inline: 6px;\n padding-block: 3px;\n }\n .purple-bg {\n background-color: #7c66dc;\n }\n .yellow-bg {\n background-color: #dcc266;\n }\n .cyan-bg {\n background-color: #0daebb;\n }\n .pink-bg {\n background-color: #d366dc;\n }\n .grey-bg {\n background-color: #818181;\n }\n .red-bg {\n background-color: #dc6666;\n }\n .green-bg {\n background-color: #04a46e;\n }\n .mustard-bg {\n background-color: #dc9866;\n }\n`;\n\nreturn (\n <Container>\n <div className={getClassNameByCategory() + \"-bg rounded-1 tag\"}>\n {category}\n </div>\n </Container>\n);\n" }, "devhub.entity.proposal.CategoryDropdown": { "": "const { selectedValue, onChange, disabled } = props;\n\nonChange = onChange || (() => {});\n\nconst options = [\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreiet5w62oeef6msfsakdskq7zkjk33ngogcerfdmqewnsuj74u376e\",\n title: \"DevDAO Operations\",\n description:\n \"Provide core operations and leadership for the DAO or infrastructure support.\",\n value: \"DevDAO Operations\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreiengkdru4fczwltjylfqeeypsdf4hb5fdxa6t67l3x2qtqgeo3pzq\",\n title: \"DevDAO Platform\",\n description:\n \"Build & maintain the interface for DevHub’s community & funding activities.\",\n value: \"DevDAO Platform\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreicpt3ulwsmptzdbtkhvxodvo7pcajcpyr35tqcbfdnaipzrx5re7e\",\n title: \"Events & Hackathons\",\n description:\n \"Organize or support events, hackathons, and local meet ups to grow communities.\",\n value: \"Events & Hackathons\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreibdrwhbouuutvrk4qt2udf4kumbyy5ebjkezobbahxvo7fyxo2ec4\",\n title: \"Engagement & Awareness\",\n description:\n \"Create content from social posts to real world swag to drive awareness to NEAR.\",\n value: \"Engagement & Awareness\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreiem2vjsp6wu3lkd4zagpm43f32egdjjzchmleky6rr2ydzhlkrxam\",\n title: \"Decentralized DevRel\",\n description:\n \"Provide support, gather feedback, and maintain docs to drive engagement.\",\n value: \"Decentralized DevRel\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreic3prsy52hwueugqj5rwualib4imguelezsbvgrxtezw4u33ldxqq\",\n title: \"Universities & Bootcamps\",\n description:\n \"Engage with students and universities globally to encourage NEAR.\",\n value: \"Universities & Bootcamps\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreigf7j5isssumbjl24zy4pr27ryfqivan3vuwu2uwsofcujhhkk7cq\",\n title: \"Tooling & Infrastructure\",\n description:\n \"Contribute code to NEAR tooling or facilitating technical decisions.\",\n value: \"Tooling & Infrastructure\",\n },\n {\n icon: \"https://ipfs.near.social/ipfs/bafkreihctatkwnvpmblgqnpw76zggfet3fmpgurqvtj7vbm3cb5r3pp52u\",\n title: \"Other\",\n description: \"Use this category if you are not sure which one to use.\",\n value: \"Other\",\n },\n];\n\nconst [isOpen, setIsOpen] = useState(false);\nconst [selectedOptionValue, setSelectedValue] = useState(selectedValue);\n\nconst toggleDropdown = () => {\n setIsOpen(!isOpen);\n};\n\nuseEffect(() => {\n if (selectedValue && selectedValue !== selectedOptionValue) {\n setSelectedValue(selectedValue);\n }\n}, [selectedValue]);\n\nuseEffect(() => {\n if (selectedValue !== selectedOptionValue) {\n onChange(selectedOptionValue);\n }\n}, [selectedOptionValue]);\n\nconst handleOptionClick = (option) => {\n setSelectedValue(option.value);\n setIsOpen(false);\n};\n\nconst Container = styled.div`\n .drop-btn {\n width: 100%;\n text-align: left;\n padding-inline: 10px;\n }\n\n .dropdown-toggle:after {\n position: absolute;\n top: 46%;\n right: 2%;\n }\n\n .dropdown-menu {\n width: 100%;\n }\n\n .dropdown-item.active,\n .dropdown-item:active {\n background-color: #f0f0f0 !important;\n color: black;\n }\n\n .disabled {\n background-color: #f8f8f8 !important;\n cursor: not-allowed !important;\n border-radius: 5px;\n opacity: inherit !important;\n }\n\n .disabled.dropdown-toggle::after {\n display: none !important;\n }\n\n .custom-select {\n position: relative;\n }\n\n .selected {\n background-color: #f0f0f0;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n`;\n\nconst Item = ({ option }) => {\n if (!option) {\n return <div className=\"text-muted\">Select Category</div>;\n }\n return (\n <div className=\"d-flex gap-3 align-items-center w-100\">\n <img src={option.icon} height={30} />\n <div className=\"d-flex flex-column gap-1 w-100 text-wrap\">\n <div className=\"h6 mb-0\"> {option.title}</div>\n <div className=\"text-sm text-muted w-100 text-wrap\">\n {option.description}\n </div>\n </div>\n </div>\n );\n};\n\nconst selectedOption =\n options.find((item) => item.value === selectedOptionValue) ?? null;\n\nreturn (\n <Container>\n <div\n className=\"custom-select w-100\"\n tabIndex=\"0\"\n onBlur={() => setIsOpen(false)}\n >\n <div\n className={\n \"dropdown-toggle bg-white border rounded-2 btn drop-btn w-100 \" +\n (disabled ? \"disabled\" : \"\")\n }\n onClick={!disabled && toggleDropdown}\n >\n <div className={`selected-option`}>\n <Item option={selectedOption} />\n </div>\n </div>\n\n {isOpen && (\n <div className=\"dropdown-menu rounded-2 dropdown-menu-end dropdown-menu-lg-start px-2 shadow show w-100\">\n <div>\n {options.map((option) => (\n <div\n key={option.value}\n className={`dropdown-item cursor-pointer w-100 my-1 ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n onClick={() => handleOptionClick(option)}\n >\n <Item option={option} />\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n </Container>\n);\n" }, "core.lib.url": { "": "/**\n * Generates a URL to a widget.\n *\n * @param {Object} options - Configuration options for constructing the URL.\n * @param {string} [options.gateway] - The gateway or server address where the widget source is hosted (optional).\n * @param {string} options.widgetSrc - The source path of the widget (required).\n * @param {Object} [options.params] - An object containing key-value pairs representing query parameters to be appended to the URL (optional).\n * @returns {string} - The constructed URL.\n */\nfunction href({ gateway, widgetSrc, params }) {\n // Check if query parameters are provided and filter out null values\n if (params) {\n params = (Object.entries(params) || [])\n .filter(([_key, nullable]) => (nullable ?? null) !== null)\n .map(([key, value]) => {\n // Omit the parameter if the value is null or the array is empty\n if (value === null || (Array.isArray(value) && value.length === 0)) {\n return null;\n }\n\n // Convert array values to a comma-separated string with no spaces\n if (Array.isArray(value)) {\n return `${key}=${value.join(\",\")}`;\n } else {\n return `${key}=${value}`;\n }\n })\n .join(\"&\");\n }\n\n // Check if the gateway already includes \"https://\" and construct the final URL accordingly\n if (gateway) {\n if (/(^https:\\/\\/)|(^http:\\/\\/)/.test(gateway)) {\n return `/${gateway}/${widgetSrc}${params && `?${params}`}`;\n } else {\n return `https://${gateway}/${widgetSrc}${params && `?${params}`}`;\n }\n } else {\n return `/${widgetSrc}${params && `?${params}`}`;\n }\n}\n\nreturn { href };\n" }, "devhub.entity.addon.blog.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} = props;\n\nconst TitleInput = ({ title, setTitle }) => {\n return (\n <div>\n <h5>Title</h5>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n onChange: (e) => setTitle(e.target.value),\n value: title,\n placeholder: \"Title\",\n inputProps: { name: \"title\" },\n }}\n />\n </div>\n </div>\n );\n};\nconst SubtitleInput = ({ subtitle, setSubtitle }) => {\n return (\n <div>\n <h5>Subtitle</h5>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n onChange: (e) => setSubtitle(e.target.value),\n value: subtitle,\n placeholder: \"Subtitle\",\n inputProps: { name: \"subtitle\" },\n }}\n />\n </div>\n </div>\n );\n};\n\nconst CategorySelect = ({ options, category, setCategory }) => {\n return (\n <div>\n <h5>Category</h5>\n <div className=\"flex-grow-1\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Select\"}\n props={{\n className: \"flex-grow-1\",\n options,\n value: category,\n onChange: (e) => setCategory(e.target.value),\n placeholder: \"Select a category\",\n }}\n />\n </div>\n </div>\n );\n};\n\nconst DescriptionInput = ({ description, setDescription }) => {\n return (\n <div>\n <h5>Description</h5>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n onChange: (e) => setDescription(e.target.value),\n value: description,\n placeholder: \"Description\",\n inputProps: { name: \"description\" },\n }}\n />\n </div>\n </div>\n );\n};\n\nconst AuthorInput = ({ author, setAuthor }) => {\n return (\n <div>\n <h5>Author</h5>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.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: { name: \"author\" },\n }}\n />\n </div>\n </div>\n );\n};\n\nconst DateInput = ({ date, setDate }) => {\n return (\n <div>\n <h5>Date</h5>\n <input\n name=\"date\"\n type=\"date\"\n value={date}\n onChange={(e) => setDate(e.target.value)}\n />\n </div>\n );\n};\n\nconst ContentEditor = ({ content, setContent }) => {\n return (\n <div>\n <h5>Content</h5>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.MarkdownEditor\"\n props={{ data: { content }, onChange: setContent }}\n />\n </div>\n );\n};\n\nreturn (\n <FormContainer id=\"blog-editor-form\">\n <TitleInput title={title} setTitle={setTitle} />\n <SubtitleInput subtitle={subtitle} setSubtitle={setSubtitle} />\n <CategorySelect\n options={options}\n category={category}\n setCategory={setCategory}\n />\n <DescriptionInput\n description={description}\n setDescription={setDescription}\n debouncedUpdateState={debouncedUpdateState}\n />\n <AuthorInput author={author} setAuthor={setAuthor} />\n <DateInput date={date} setDate={setDate} />\n <ContentEditor content={content} setContent={setContent} />\n </FormContainer>\n);\n" }, "core.lib.common": { "": "// https://docs.rs/near-sdk/latest/near_sdk/env/constant.STORAGE_PRICE_PER_BYTE.html\nconst STORAGE_PRICE_PER_BYTE = \"10000000000000000000\";\n// https://github.com/NearSocial/social-db/blob/d28c647252ce25a06c70c3b7f4930ccdcd217fd9/contract/src/account.rs#L8C5-L8C50\nconst MIN_STORAGE_BYTES = \"2000\";\nconst MIN_STORAGE_COST = Big(STORAGE_PRICE_PER_BYTE).times(MIN_STORAGE_BYTES);\n\n// in case the user is new and doesn't have min storage cost, increasing the deposit\nfunction getDepositAmountForWriteAccess(userStorageDeposit) {\n const depositAmt = Big(userStorageDeposit?.available).gt(MIN_STORAGE_COST)\n ? Big(10).pow(22)\n : Big(MIN_STORAGE_COST).plus(Big(10).pow(22));\n\n return depositAmt;\n}\n\nfunction readableDate(timestamp) {\n var a = new Date(timestamp);\n var options = {\n month: \"short\",\n day: \"2-digit\",\n year: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n timeZone: \"UTC\",\n };\n return a.toLocaleString(\"en-US\", options) + \" UTC\";\n}\n\nreturn { getDepositAmountForWriteAccess, readableDate };\n" }, "devhub.entity.addon.blog.Viewer": { "": "const { Card } =\n VM.require(\"devhub.megha19.near/widget/devhub.entity.addon.blog.Card\") ||\n (() => <></>);\n\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nconst { includeLabels, excludeLabels, layout, handle, hideTitle } = props;\n\nconst Grid = styled.div`\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 1rem;\n\n @media screen and (max-width: 768px) {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n }\n`;\n\nconst Heading = styled.h3`\n color: #151515;\n font-size: 2rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 48px */\n margin-bottom: 2rem;\n\n @media screen and (max-width: 768px) {\n font-size: 1.5rem;\n }\n`;\n\nconst CardContainer = styled.div`\n transition: all 300ms;\n border-radius: 1rem;\n height: 100%;\n\n &:hover {\n box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1),\n 0 4px 6px -4px rgb(0 0 0 / 0.1);\n }\n`;\n\nfunction BlogCard(postId) {\n return (\n <Link\n style={{ textDecoration: \"none\" }}\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"blog\", id: postId },\n })}\n >\n <CardContainer>\n <Widget // We need this so the individual posts can make the necessary call for more data\n src=\"devhub.megha19.near/widget/devhub.entity.post.Postv2\"\n props={{ postKey: postId, template: (p) => <Card {...(p || {})} /> }} // I wonder if this could take list of types, their templates, normalizer functions, etc... and have this all as a module\n />\n {/* // so then you could swap between devhub contract or social contract sources, it doesn't matter. */}\n </CardContainer>\n </Link>\n );\n}\n\nreturn (\n <div class=\"w-100\">\n {!hideTitle && <Heading>Latest Blog Posts</Heading>}\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.addon.blog.Feed\"}\n // TODO: This needs to filter by more labels\n props={{\n includeLabels: [\"blog\", handle, ...(includeLabels || [])], // make sure this has the community handle\n excludeLabels: excludeLabels || [],\n renderItem: BlogCard,\n Layout: ({ children }) => <Grid>{children}</Grid>,\n }}\n />\n </div>\n);\n" }, "devhub.components.organism.Feed.NearQueryApi": { "": "const LIMIT = 10;\nconst filteredAccountIds = props.filteredAccountIds;\nconst setPostExists = props.setPostExists ?? (() => {});\nconst GRAPHQL_ENDPOINT =\n props.GRAPHQL_ENDPOINT ?? \"https://near-queryapi.api.pagoda.co\";\n\nconst sort = props.sort || \"desc\";\n\n// get the full list of posts that the current user has flagged so\n// they can be hidden\nconst selfFlaggedPosts = context.accountId\n ? Social.index(\"flag\", \"main\", {\n accountId: context.accountId,\n }) ?? []\n : [];\n\n// V2 self moderation data, structure is like:\n// { moderate: {\n// \"account1.near\": \"report\",\n// \"account2.near\": {\n// \".post.main\": { // slashes are not allowed in keys\n// \"100000123\": \"spam\", // post ids are account/blockHeight\n// }\n// },\n// }\n// }\nconst selfModeration = context.accountId\n ? Social.getr(`${context.accountId}/moderate`, \"optimistic\") ?? []\n : [];\nconst postsModerationKey = \".post.main\";\nconst commentsModerationKey = \".post.comment\";\nconst matchesModeration = (moderated, socialDBObjectType, item) => {\n if (!moderated) return false;\n const accountFound = moderated[item.account_id];\n if (typeof accountFound === \"undefined\") {\n return false;\n }\n if (typeof accountFound === \"string\" || accountFound[\"\"]) {\n return true;\n }\n const moderatedItemsOfType = accountFound[socialDBObjectType];\n return (\n moderatedItemsOfType &&\n typeof moderatedItemsOfType[item.block_height] !== \"undefined\"\n );\n};\n\nconst shouldFilter = (item, socialDBObjectType) => {\n return (\n selfFlaggedPosts.find((flagged) => {\n return (\n flagged?.value?.blockHeight === item.block_height &&\n flagged?.value?.path.includes(item.account_id)\n );\n }) || matchesModeration(selfModeration, socialDBObjectType, item)\n );\n};\nfunction fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(`${GRAPHQL_ENDPOINT}/v1/graphql`, {\n method: \"POST\",\n headers: { \"x-hasura-role\": \"dataplatform_near\" },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n}\n\nconst createQuery = (type, isUpdate) => {\n let querySortOption = \"\";\n switch (sort) {\n case \"recentcommentdesc\":\n querySortOption = `{ last_comment_timestamp: desc_nulls_last },`;\n break;\n default:\n querySortOption = \"\";\n }\n\n let queryFilter = \"\";\n let timeOperation = \"_lte\";\n if (isUpdate) {\n timeOperation = \"_gt\";\n }\n\n const queryTime = initialQueryTime ? initialQueryTime : Date.now() * 1000000;\n\n if (filteredAccountIds) {\n queryFilter = `where: {\n _and: [\n {account_id: {_in: \"${filteredAccountIds}\"}},\n {block_timestamp: {${timeOperation}: ${queryTime}}}\n ]\n }, `;\n } else {\n queryFilter = `where: {\n _and: [\n {block_timestamp: {${timeOperation}: ${queryTime}}}\n ]\n }, `;\n }\n\n return `\nquery FeedQuery($offset: Int, $limit: Int) {\n dataplatform_near_social_feed_moderated_posts(${queryFilter} order_by: [${querySortOption} { block_height: desc }], offset: $offset, limit: $limit) {\n account_id\n block_height\n block_timestamp\n content\n receipt_id\n accounts_liked\n last_comment_timestamp\n comments(order_by: {block_height: asc}) {\n account_id\n block_height\n block_timestamp\n content\n }\n verifications {\n human_provider\n human_valid_until\n human_verification_level\n }\n\n }\n dataplatform_near_social_feed_moderated_posts_aggregate(${queryFilter} order_by: {id: asc}) {\n aggregate {\n count\n }\n }\n}\n`;\n};\n\nconst loadMorePosts = (isUpdate) => {\n const queryName = \"FeedQuery\";\n\n if (!isUpdate) {\n setIsLoading(true);\n }\n const offset = isUpdate ? 0 : postsData.posts.length;\n const limit = isUpdate ? 100 : LIMIT;\n const query = createQuery(\"\", isUpdate);\n fetchGraphQL(query, queryName, {\n offset: offset,\n limit: limit,\n }).then((result) => {\n if (result.status === 200 && result.body) {\n if (result.body.errors) {\n console.log(\"error:\", result.body.errors);\n return;\n }\n let data = result.body.data;\n if (data) {\n const newPosts = data.dataplatform_near_social_feed_moderated_posts;\n const postsCountLeft =\n data.dataplatform_near_social_feed_moderated_posts_aggregate.aggregate\n .count;\n if (newPosts.length > 0) {\n let filteredPosts = newPosts.filter(\n (i) => !shouldFilter(i, postsModerationKey)\n );\n filteredPosts = filteredPosts.map((post) => {\n const prevComments = post.comments;\n const filteredComments = prevComments.filter(\n (comment) => !shouldFilter(comment, commentsModerationKey)\n );\n post.comments = filteredComments;\n return post;\n });\n\n if (isUpdate) {\n setNewUnseenPosts(filteredPosts);\n } else {\n setPostsData({\n posts: [...postsData.posts, ...filteredPosts],\n postsCountLeft,\n });\n setIsLoading(false);\n }\n }\n }\n }\n if (!isUpdate && initialQueryTime === null) {\n const newTime =\n postsData.posts && postsData.posts[0]\n ? postsData.posts[0].block_timestamp\n : Date.now() * 1000000;\n setInitialQueryTime(newTime + 1000);\n }\n });\n};\n\nconst startFeedUpdates = () => {\n if (initialQueryTime === null) return;\n\n clearInterval(feedInterval);\n const newFeedInterval = setInterval(() => {\n loadMorePosts(true);\n }, 5000);\n setFeedInterval(newFeedInterval);\n};\n\nconst stopFeedUpdates = () => {\n clearInterval(feedInterval);\n};\n\nconst [initialized, setInitialized] = useState(false);\nconst [initialQueryTime, setInitialQueryTime] = useState(null);\nconst [feedInterval, setFeedInterval] = useState(null);\nconst [newUnseenPosts, setNewUnseenPosts] = useState([]);\nconst [postsData, setPostsData] = useState({ posts: [], postsCountLeft: 0 });\nconst [isLoading, setIsLoading] = useState(false);\n\nuseEffect(() => {\n loadMorePosts(false);\n}, []);\n\nuseEffect(() => {\n if (postsData.posts.length > 0) {\n setPostExists(true);\n }\n}, [postsData]);\n\nuseEffect(() => {\n if (initialQueryTime === null) {\n clearInterval(feedInterval);\n } else {\n startFeedUpdates();\n }\n}, [initialQueryTime]);\n\nuseEffect(() => {\n if (newUnseenPosts && newUnseenPosts.length > 0) {\n stopFeedUpdates();\n const initialQueryTime = newUnseenPosts[0].block_timestamp + 1000; // timestamp is getting rounded by 3 digits\n const newTotalCount = postsData.postsCountLeft + newUnseenPosts.length;\n setPostsData({\n posts: [...newUnseenPosts, ...postsData.posts],\n postsCountLeft: newTotalCount,\n });\n if (props.onNewUnseenPosts) {\n props.onNewUnseenPosts(newUnseenPosts);\n }\n setNewUnseenPosts([]);\n setInitialQueryTime(initialQueryTime);\n }\n}, [newUnseenPosts]);\n\nconst hasMore =\n postsData.postsCountLeft !== postsData.posts.length &&\n postsData.posts.length > 0;\n\nif (!initialized && sort) {\n setInitialized(true);\n}\n\nreturn (\n <>\n <Widget\n src=\"near/widget/Posts.Feed\"\n props={{\n hasMore,\n isLoading,\n loadMorePosts: () => {\n if (!isLoading) {\n loadMorePosts(false);\n }\n },\n posts: postsData.posts,\n showFlagAccountFeature: props.showFlagAccountFeature,\n }}\n />\n </>\n);\n" }, "devhub.entity.community.Teams": { "": "const { handle } = props;\n\nconst { Tile } =\n VM.require(\"devhub.megha19.near/widget/devhub.components.molecule.Tile\") ||\n (() => <></>);\n\nif (!Tile) {\n return <div>Loading...</div>;\n}\n\nconst { getCommunity } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nconst communityData = getCommunity({ handle });\n\nif (communityData === null) {\n return <div>Loading...</div>;\n}\n\nconst UserList = ({ name, users }) => (\n <div>\n {(users ?? []).map((user, i) => (\n <div className={`row ${i < users.length - 1 ? \"mb-3\" : \"\"}`}>\n <div class=\"col-3\">\n <b>{name + \" #\" + (i + 1)}</b>\n </div>\n\n <div class=\"col-9\">\n <span\n key={user}\n className=\"d-inline-flex\"\n style={{ fontWeight: 500 }}\n >\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.ProfileLine\"\n props={{ accountId: user, hideAccountId: true, tooltip: true }}\n />\n </span>\n </div>\n </div>\n ))}\n </div>\n);\n\nreturn (\n <div className=\"d-flex flex-column align-items-center gap-4 w-100 px-2\">\n <Tile className=\"p-3 w-100 bg-white mb-3\" style={{ maxWidth: 960 }}>\n <div>\n <div\n className=\"d-flex align-items-center justify-content-between w-100 pb-3\"\n style={{ minHeight: 30 }}\n >\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n <span>Community Admins</span>\n </h5>\n </div>\n <UserList name=\"Admin\" users={communityData.admins} />\n </div>\n </Tile>\n </div>\n);\n" }, "devhub.entity.team.LabelRow": { "": "/**\n * In the context of the contract, a group is essentially a member identified\n * by the prefix 'team:'; therefore, on the front end, we also employ 'team,'\n * with the user interface displaying 'group' for clarity.\n */\n\nconst { getAccessControlInfo, getRootMembers, removeMember } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nif (!getAccessControlInfo || !getRootMembers || !removeMember) {\n return <p>Loading modules...</p>;\n}\n\nconst accessControlInfo = getAccessControlInfo();\nconst rootMembers = getRootMembers();\nconst allTeamNames = Object.keys(rootMembers || {});\n\nif (!accessControlInfo || !rootMembers) {\n return <p>Loading access control info...</p>;\n}\n\nconst { teamName } = props;\nconst teamModerators = teamName == \"team:moderators\";\nconst label = Object.keys(rootMembers[teamName].permissions)[0] || \"\";\nconst metadata = accessControlInfo.members_list[teamName];\nconst editPost = rootMembers[teamName].permissions[label].includes(\"edit-post\");\nconst useLabels =\n rootMembers[teamName].permissions[label].includes(\"use-labels\");\nconst members = rootMembers[teamName].children || [];\n\nconst configuratorData = {\n teamName: teamName,\n label: label,\n members,\n editPost,\n useLabels,\n};\n\nconst [editMode, setEditMode] = useState(false);\nconst [alertMessage, setAlertMessage] = useState(\"\");\n\nfunction arrayEq(arr1, arr2) {\n if (arr1.length !== arr2.length) {\n return false;\n }\n const sortedArr1 = arr1.slice().sort();\n const sortedArr2 = arr2.slice().sort();\n for (let i = 0; i < sortedArr1.length; i++) {\n if (sortedArr1[i] !== sortedArr2[i]) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction editTeam({\n teamName: tmnm,\n label: lbl,\n editPost: edtpst,\n useLabels: uslbls,\n members: mmbrs,\n}) {\n let txn = [];\n let numberOfChanges = 0;\n\n if (backwardsCompatibleTeam(teamName) !== tmnm) {\n numberOfChanges++;\n if (allTeamNames.includes(`team:${tmnm}`)) {\n return setAlertMessage(\"This team name already exists\");\n }\n }\n\n console.log(label, lbl);\n if (label !== lbl) {\n const allLabels = Object.keys(accessControlInfo.rules_list);\n if (allLabels.includes(lbl)) {\n return setAlertMessage(\n \"This label is already restricted by another team\"\n );\n }\n }\n\n if (editPost !== edtpst || useLabels !== uslbls) {\n numberOfChanges++;\n }\n\n if (!arrayEq(members, mmbrs)) {\n numberOfChanges++;\n let membersAndTeams = Object.keys(accessControlInfo.members_list);\n\n mmbrs.forEach((member) => {\n if (!membersAndTeams.includes(member)) {\n // Contract panic member does not exist in the members_list yet.\n txn.push({\n contractName: \"devhub.near\",\n methodName: \"add_member\",\n args: {\n member: member,\n metadata: {\n member_metadata_version: \"V0\",\n description: \"\",\n permissions: {},\n children: [],\n parents: [],\n },\n },\n gas: Big(10).pow(14),\n });\n }\n });\n }\n\n if (numberOfChanges < 1) {\n return setAlertMessage(\"No changes found.\");\n }\n\n Near.call([\n ...txn,\n {\n contractName: \"devhub.near\",\n methodName: \"edit_member\",\n args: {\n member: `team:${tmnm}`,\n metadata: {\n member_metadata_version: \"V0\",\n description: \"\",\n permissions: {\n [lbl]: [\n ...(edtpst ? [\"edit-post\"] : []),\n ...(uslbls ? [\"use-labels\"] : []),\n ],\n },\n children: mmbrs,\n parents: [],\n },\n },\n gas: Big(10).pow(14),\n },\n ]);\n}\n\nfunction deleteLabel() {\n // contract side this is called a team / member\n removeMember(teamName);\n}\n\nconst backwardsCompatibleLabel = (oldLabel) => {\n if (typeof oldLabel === \"string\")\n return oldLabel.startsWith(\"starts-with:\") ? oldLabel.slice(12) : oldLabel;\n else return \"\";\n};\n// Teams are saved in contract by their prefix 'team:'\n// This function makes the teamName display friendly.\nconst backwardsCompatibleTeam = (oldTeam) =>\n oldTeam.startsWith(\"team:\") ? oldTeam.slice(5) : oldTeam;\n\nreturn (\n <>\n <tr>\n <th scope=\"row\" class=\" justify-content-center align-items-center p-3\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.atom.Tag\"}\n props={{\n tag: backwardsCompatibleLabel(label),\n }}\n />\n </th>\n <td class=\" justify-content-center align-items-center p-3\">\n {(label || \"\").startsWith(\"starts-with:\")\n ? \"Multiple labels with common prefix\"\n : \"Single label\"}\n </td>\n <td class=\" justify-content-center align-items-center p-3\">\n {metadata.children && (\n <div class=\"vstack\">\n {metadata.children.length ? (\n metadata.children.map((child) => <p>{child}</p>)\n ) : (\n <div>No members in this group</div>\n )}\n </div>\n )}\n </td>\n <td class=\" justify-content-center align-items-center p-3\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n value={useLabels}\n id={`useLabelsCheckbox${identifier}`}\n checked={useLabels}\n onChange={() => setUseLabels(!useLabels)}\n disabled={disabled}\n />\n </div>\n </td>\n <td class=\" justify-content-center align-items-center p-3\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n value={editPost}\n id={`editPostCheckbox${identifier}`}\n checked={editPost}\n onChange={() => setEditPost(!editPost)}\n disabled={disabled}\n />\n </div>\n </td>\n <td class=\" justify-content-center align-items-center p-3\">\n {editMode ? (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-danger\" },\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-trash\",\n },\n label: \"Delete\",\n onClick: deleteLabel,\n }}\n />\n ) : (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-light text-dark\" },\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-gear-wide-connected\",\n },\n label: \"Edit\",\n onClick: () => setEditMode(true),\n }}\n />\n )}\n </td>\n </tr>\n {editMode && (\n <tr>\n <th scope=\"row\" colspan=\"6\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.team.Configurator\"}\n props={{\n data: configuratorData,\n onCancel: () => setEditMode(false),\n onSubmit: (params) => editTeam(params),\n }}\n />\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.atom.Alert\"\n props={{\n onClose: () => setAlertMessage(\"\"),\n message: alertMessage,\n }}\n />\n </th>\n </tr>\n )}\n </>\n);\n" }, "devhub.components.organism.NewsLetter": { "": "const page = props.page;\n\nconst Footer = styled.div`\n width: 100%;\n background-color: #00ec97;\n padding: 2rem;\n margin-top: 2rem;\n width: 100vw;\n position: relative;\n left: 50%;\n right: 50%;\n margin-left: -50vw;\n margin-right: -50vw;\n\n @media screen and (max-width: 768px) {\n padding: 1rem;\n }\n`;\n\nconst CTA = styled.a`\n display: inline-flex;\n padding: 0.5rem 0.8rem;\n align-items: center;\n gap: 0.5rem;\n\n border-radius: 0.5rem;\n border: 1px solid #151515;\n\n color: #151515 !important;\n font-size: 0.8rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.48px;\n\n &:hover {\n background: #151515;\n color: #f4f4f4 !important;\n text-decoration: none; // Remove underline on hover\n }\n`;\n\nconst XIcon = () => {\n return (\n <svg\n width=\"20\"\n height=\"16\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 300 300\"\n >\n <path d=\"M178.57 127.15 290.27 0h-26.46l-97.03 110.38L89.34 0H0l117.13 166.93L0 300.25h26.46l102.4-116.59 81.8 116.59h89.34M36.01 19.54H76.66l187.13 262.13h-40.66\" />\n </svg>\n );\n};\n\nconst TelegramIcon = () => {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"20\"\n height=\"16\"\n viewBox=\"0 0 20 16\"\n fill=\"none\"\n >\n <path\n d=\"M19.7398 1.45657L16.8608 15.0342C16.6436 15.9924 16.0771 16.2309 15.2721 15.7796L10.8854 12.5469L8.76879 14.5828C8.53463 14.817 8.33866 15.0129 7.8872 15.0129L8.20233 10.5452L16.3327 3.19847C16.6862 2.88334 16.256 2.70869 15.7833 3.02386L5.73217 9.35266L1.40507 7.99835C0.463838 7.70445 0.446834 7.05707 1.60095 6.60566L18.526 0.085202C19.3096 -0.208647 19.9954 0.25977 19.7398 1.45657Z\"\n fill=\"#151515\"\n />\n </svg>\n );\n};\n\nconst YoutubeIcon = () => {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"16\"\n viewBox=\"0 0 24 16\"\n fill=\"none\"\n >\n <path\n d=\"M23.1744 2.49854C22.9115 1.51517 22.1369 0.740571 21.1535 0.477714C19.3712 -1.21102e-07 12.2235 0 12.2235 0C12.2235 0 5.07581 -1.21102e-07 3.29346 0.477714C2.3101 0.740571 1.53549 1.51517 1.27264 2.49854C0.794922 4.28089 0.794922 8 0.794922 8C0.794922 8 0.794922 11.7191 1.27264 13.5015C1.53549 14.4848 2.3101 15.2594 3.29346 15.5223C5.07581 16 12.2235 16 12.2235 16C12.2235 16 19.3712 16 21.1535 15.5223C22.1369 15.2594 22.9115 14.4848 23.1744 13.5015C23.6521 11.7191 23.6521 8 23.6521 8C23.6521 8 23.6521 4.28089 23.1744 2.49854ZM9.93778 11.4286V4.57143L15.8761 8L9.93778 11.4286Z\"\n fill=\"#151515\"\n />\n </svg>\n );\n};\n\nconst MidContent = () => {\n return (\n <>\n <MidContainer>\n <Title>/dev/hub newsletter</Title>\n <Description>\n Stay in the loop. Get the latest updates, announcements,\n <br />\n opportunities, and insights from the ecosystem in your inbox.\n </Description>\n <CTA href=\"https://newsletter.neardevhub.org\" target=\"no_blank\">\n Subscribe\n </CTA>\n <SocialLinks>\n <a href=\"https://twitter.com/NEARDevHub\" target=\"_blank\">\n <XIcon />\n </a>\n <a href=\"https://t.me/NEARDevHub\" target=\"_blank\">\n <TelegramIcon />\n </a>\n <a href=\"https://www.youtube.com/@NEARDevHub\" target=\"_blank\">\n <YoutubeIcon />\n </a>\n </SocialLinks>\n </MidContainer>\n </>\n );\n};\n\nconst SocialLinksContainer = () => {\n return (\n <div className=\"d-flex gap-md-4 gap-2 align-items-center\">\n <a href=\"https://twitter.com/NEARDevHub\" target=\"_blank\">\n <XIcon />\n </a>\n <a href=\"https://t.me/NEARDevHub\" target=\"_blank\">\n <TelegramIcon />\n </a>\n <a href=\"https://www.youtube.com/@NEARDevHub\" target=\"_blank\">\n <YoutubeIcon />\n </a>\n </div>\n );\n};\n\nreturn (\n <Footer className=\"d-flex gap-2 justify-content-between\">\n <SocialLinksContainer />\n <div className=\"d-flex align-items-center gap-3\">\n <h6 className=\"m-0\">Subscribe to our newsletter</h6>\n <CTA href=\"https://newsletter.neardevhub.org\" target=\"no_blank\">\n Subscribe\n </CTA>\n </div>\n </Footer>\n);\n" }, "devhub.entity.community.Compose": { "": "const profileAccountId = props.profileAccountId;\n\nif (!profileAccountId) {\n return <></>;\n}\n\nState.init({\n image: {},\n text: \"\",\n showPreview: false,\n mentionInput: \"\", // text next to @ tag\n mentionsArray: [], // all the mentions in the description\n});\n\nconst [isSubmittingTransaction, setIsSubmittingTransaction] = useState(false);\nconst profile = Social.getr(`${profileAccountId}/profile`);\nconst autocompleteEnabled = true;\n\nconst content = {\n type: \"md\",\n image: state.image.cid ? { ipfs_cid: state.image.cid } : undefined,\n text: state.text,\n};\n\nfunction extractMentions(text) {\n const mentionRegex =\n /@((?:(?:[a-z\\d]+[-_])*[a-z\\d]+\\.)*(?:[a-z\\d]+[-_])*[a-z\\d]+)/gi;\n mentionRegex.lastIndex = 0;\n const accountIds = new Set();\n for (const match of text.matchAll(mentionRegex)) {\n if (\n !/[\\w`]/.test(match.input.charAt(match.index - 1)) &&\n !/[/\\w`]/.test(match.input.charAt(match.index + match[0].length)) &&\n match[1].length >= 2 &&\n match[1].length <= 64\n ) {\n accountIds.add(match[1].toLowerCase());\n }\n }\n return [...accountIds];\n}\n\nfunction extractTagNotifications(text, item) {\n return extractMentions(text || \"\")\n .filter((accountId) => accountId !== profileAccountId)\n .map((accountId) => ({\n key: accountId,\n value: {\n type: \"mention\",\n item,\n },\n }));\n}\n\nfunction composeData() {\n const data = {\n post: {\n main: JSON.stringify(content),\n },\n index: {\n post: JSON.stringify({\n key: \"main\",\n value: {\n type: \"md\",\n },\n }),\n },\n };\n\n const notifications = extractTagNotifications(state.text, {\n type: \"social\",\n path: `${profileAccountId}/post/main`,\n });\n\n if (notifications.length) {\n data.index.notify = JSON.stringify(\n notifications.length > 1 ? notifications : notifications[0]\n );\n }\n\n return data;\n}\n\nconst handleSubmit = () => {\n const data = composeData();\n if (props.onSubmit) {\n props.onSubmit(data);\n }\n if (props.isFinished) {\n setIsSubmittingTransaction(true);\n }\n};\n\nfunction resetState() {\n State.update({\n image: {},\n text: \"\",\n });\n}\n\nuseEffect(() => {\n if (props.isFinished && props.isFinished() && isSubmittingTransaction) {\n resetState();\n setIsSubmittingTransaction(false);\n }\n}, [props.isFinished]);\n\nfunction textareaInputHandler(value) {\n const words = value.split(/\\s+/);\n const allMentiones = words\n .filter((word) => word.startsWith(\"@\"))\n .map((mention) => mention.slice(1));\n const newMentiones = allMentiones.filter(\n (item) => !state.mentionsArray.includes(item)\n );\n\n State.update((lastKnownState) => ({\n ...lastKnownState,\n text: value,\n showAccountAutocomplete: newMentiones?.length > 0,\n mentionsArray: allMentiones,\n mentionInput: newMentiones?.[0] ?? \"\",\n }));\n}\n\nfunction autoCompleteAccountId(id) {\n // to make sure we update the @ at correct index\n let currentIndex = 0;\n const updatedDescription = state.text.replace(\n /(?:^|\\s)(@[^\\s]*)/g,\n (match) => {\n if (currentIndex === state.mentionsArray.indexOf(state.mentionInput)) {\n currentIndex++;\n return ` @${id}`;\n } else {\n currentIndex++;\n return match;\n }\n }\n );\n State.update((lastKnownState) => ({\n ...lastKnownState,\n text: updatedDescription,\n showAccountAutocomplete: false,\n }));\n}\n\nconst Wrapper = styled.div`\n --padding: 24px;\n position: relative;\n\n @media (max-width: 1024px) {\n --padding: 12px;\n }\n`;\n\nconst LoadingButtonSpinner = (\n <span\n class=\"submit-post-loading-indicator spinner-border spinner-border-sm\"\n role=\"status\"\n aria-hidden=\"true\"\n ></span>\n);\n\nconst Avatar = styled.div`\n width: 40px;\n height: 40px;\n pointer-events: none;\n position: absolute;\n top: var(--padding);\n left: var(--padding);\n\n img {\n object-fit: cover;\n border-radius: 40px;\n width: 100%;\n height: 100%;\n }\n\n @media (max-width: 992px) {\n display: none;\n }\n`;\n\nconst Textarea = styled.div`\n display: grid;\n vertical-align: top;\n align-items: center;\n position: relative;\n align-items: stretch;\n\n &::after,\n textarea {\n width: 100%;\n min-width: 1em;\n height: unset;\n min-height: 164px;\n font: inherit;\n padding: var(--padding) var(--padding) calc(40px + (var(--padding) * 2))\n calc(40px + (var(--padding) * 2));\n margin: 0;\n resize: none;\n background: none;\n appearance: none;\n border: none;\n grid-area: 1 / 1;\n overflow: hidden;\n outline: none;\n\n @media (max-width: 1024px) {\n min-height: 124px;\n }\n\n @media (max-width: 992px) {\n padding-left: var(--padding);\n }\n }\n\n &::after {\n content: attr(data-value) \" \";\n visibility: hidden;\n white-space: pre-wrap;\n }\n\n textarea {\n transition: all 200ms;\n\n &::placeholder {\n opacity: 1;\n color: #687076;\n }\n\n &:empty + p {\n display: block;\n }\n }\n`;\n\nconst TextareaDescription = styled.p`\n position: absolute;\n top: calc(var(--padding) + 24px);\n left: calc(42px + (var(--padding) * 2));\n right: var(--padding);\n font-size: 10px;\n line-height: 18px;\n font-weight: 400;\n color: #687076;\n pointer-events: none;\n display: none;\n\n a {\n color: #000;\n outline: none;\n font-weight: 600;\n pointer-events: auto;\n\n &:hover,\n &:focus {\n color: #000;\n text-decoration: underline;\n }\n }\n\n @media (max-width: 992px) {\n left: var(--padding);\n }\n`;\n\nconst Actions = styled.div`\n display: inline-flex;\n gap: 12px;\n position: absolute;\n bottom: var(--padding);\n right: var(--padding);\n\n .commit-post-button,\n .preview-post-button {\n background: #59e692;\n color: #09342e;\n border-radius: 40px;\n height: 40px;\n padding: 0 35px;\n font-weight: 600;\n font-size: 14px;\n border: none;\n cursor: pointer;\n transition: background 200ms, opacity 200ms;\n\n &:hover,\n &:focus {\n background: rgb(112 242 164);\n outline: none;\n }\n\n &:disabled {\n opacity: 0.5;\n pointer-events: none;\n }\n }\n\n .preview-post-button {\n color: #11181c;\n background: #f1f3f5;\n padding: 0;\n width: 40px;\n\n &:hover,\n &:focus {\n background: #d7dbde;\n outline: none;\n }\n }\n\n .upload-image-button {\n display: flex;\n align-items: center;\n justify-content: center;\n background: #f1f3f5;\n color: #11181c;\n border-radius: 40px;\n height: 40px;\n min-width: 40px;\n font-size: 0;\n border: none;\n cursor: pointer;\n transition: background 200ms, opacity 200ms;\n\n &::before {\n font-size: 16px;\n }\n\n &:hover,\n &:focus {\n background: #d7dbde;\n outline: none;\n }\n\n &:disabled {\n opacity: 0.5;\n pointer-events: none;\n }\n\n span {\n margin-left: 12px;\n }\n }\n\n .d-inline-block {\n display: flex !important;\n gap: 12px;\n margin: 0 !important;\n\n .overflow-hidden {\n width: 40px !important;\n height: 40px !important;\n }\n }\n`;\n\nconst PreviewWrapper = styled.div`\n position: relative;\n padding: var(--padding);\n padding-bottom: calc(40px + (var(--padding) * 2));\n`;\n\nconst AutoComplete = styled.div`\n position: absolute;\n z-index: 5;\n bottom: 0;\n left: 0;\n right: 0;\n\n > div > div {\n padding: calc(var(--padding) / 2);\n }\n`;\n\nreturn (\n <Wrapper>\n {state.showPreview ? (\n <PreviewWrapper>\n <Widget\n src=\"near/widget/v1.Posts.Post\"\n loading={<div className=\"w-100\" style={{ height: \"200px\" }} />}\n props={{\n accountId: profileAccountId,\n blockHeight: \"now\",\n content,\n }}\n />\n </PreviewWrapper>\n ) : (\n <>\n <Avatar>\n <Widget\n src=\"mob.near/widget/Image\"\n props={{\n image: profile.image,\n alt: profile.name,\n fallbackUrl:\n \"https://ipfs.near.social/ipfs/bafkreibiyqabm3kl24gcb2oegb7pmwdi6wwrpui62iwb44l7uomnn3lhbi\",\n }}\n />\n </Avatar>\n\n <Textarea data-value={state.text}>\n <textarea\n data-testid=\"compose-announcement\"\n placeholder=\"What's happening?\"\n onInput={(event) => textareaInputHandler(event.target.value)}\n onKeyUp={(event) => {\n if (event.key === \"Escape\") {\n State.update({ showAccountAutocomplete: false });\n }\n }}\n value={state.text}\n />\n\n <TextareaDescription>\n <a\n href=\"https://www.markdownguide.org/basic-syntax/\"\n target=\"_blank\"\n >\n Markdown\n </a>\n is supported\n </TextareaDescription>\n </Textarea>\n </>\n )}\n\n {autocompleteEnabled && state.showAccountAutocomplete && (\n <AutoComplete>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.AccountAutocomplete\"\n props={{\n term: state.mentionInput,\n onSelect: autoCompleteAccountId,\n onClose: () => State.update({ showAccountAutocomplete: false }),\n }}\n />\n </AutoComplete>\n )}\n\n <Actions>\n {!state.showPreview && (\n <IpfsImageUpload\n image={state.image}\n className=\"upload-image-button bi bi-image\"\n />\n )}\n\n <button\n type=\"button\"\n disabled={!state.text}\n className=\"preview-post-button\"\n title={state.showPreview ? \"Edit Post\" : \"Preview Post\"}\n onClick={() => State.update({ showPreview: !state.showPreview })}\n >\n {state.showPreview ? (\n <i className=\"bi bi-pencil\" />\n ) : (\n <i className=\"bi bi-eye-fill\" />\n )}\n </button>\n\n <button\n data-testid=\"post-btn\"\n disabled={isSubmittingTransaction || (!state.text && !state.image)}\n onClick={handleSubmit}\n className=\"commit-post-button\"\n >\n {isSubmittingTransaction ? LoadingButtonSpinner : <></>}\n Post\n </button>\n </Actions>\n </Wrapper>\n);\n" }, "devhub.components.molecule.BadgesList": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nconst nearDevGovBadgesContractId = \"neardevgov.near\";\n\nlet badges = props.badges;\nconst mode = props.mode || \"normal\";\n\nif (!badges) {\n const accountId = props.accountId || context.accountId;\n const ownedBadges = Near.view(\n nearDevGovBadgesContractId,\n \"nft_tokens_for_owner\",\n {\n account_id: accountId,\n }\n );\n if (!ownedBadges) {\n return <>{mode === \"compact\" ? \"\" : \"Loading...\"}</>;\n }\n badges = ownedBadges;\n}\n\nlet style;\nif (mode === \"normal\") {\n style = { width: \"3em\", height: \"3em\" };\n} else if (mode === \"compact\") {\n style = { width: \"1.5em\", height: \"1.5em\" };\n}\nif (props.style) {\n style = props.style;\n}\n\nconst renderedBadgesList = badges.map(({ token_id: tokenId, metadata }) => (\n <Link\n to={`/neardevgov.near/widget/BadgeDetails?tokenId=${tokenId}`}\n title={`NEAR DevGov Badge - ${metadata.title}`}\n >\n <Widget\n src=\"mob.near/widget/NftImage\"\n props={{\n style,\n nft: {\n tokenMetadata: metadata,\n contractId: nearDevGovBadgesContractId,\n },\n alt: `NEAR DevGov Badge - ${metadata.title}`,\n }}\n />\n {mode === \"compact\" ? null : metadata.title}\n </Link>\n));\n\nif (mode === \"compact\") {\n return <>{renderedBadgesList}</>;\n} else {\n return (\n <ul>\n {renderedBadgesList.map((renderedBadge) => (\n <li style={{ listStyleType: \"none\" }}>{renderedBadge}</li>\n ))}\n </ul>\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}) => {\n State.init({\n data: data,\n selectedTab: \"editor\",\n });\n\n useEffect(() => {\n onChange(state.data);\n }, [state.data]);\n\n useEffect(() => {\n if (data !== state.data) {\n State.update({ data: data, handler: \"autocompleteSelected\" });\n }\n }, [data]);\n\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={() => State.update({ selectedTab: \"editor\" })}\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={\"devhub.megha19.near/widget/devhub.components.molecule.SimpleMDE\"}\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 }}\n />\n </>\n ) : (\n <div className=\"card-body\">\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"\n }\n props={{\n text: state.data,\n }}\n />\n </div>\n )}\n </div>\n </Wrapper>\n );\n};\n\nreturn Compose(props);\n" }, "devhub.entity.post.History": { "": "/*\n---props---\nprops.post: {};\nprops.id: number;\nprops.newTab: boolean;\nprops.timestamp: number;\nprops.referral: any;\n*/\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nconst postId = props.post.id ?? (props.id ? parseInt(props.id) : 0);\nconst post =\n props.post ??\n Near.view(nearDevGovGigsContractAccountId, \"get_post\", {\n post_id: postId,\n });\nif (!post || !post.snapshot_history) {\n return <div class=\"bi bi-clock-history px-2\"></div>;\n}\nconst referral = props.referral;\n\nfunction readableDate(timestamp) {\n var a = new Date(timestamp);\n return (\n a.toDateString() +\n \" \" +\n a.toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\" })\n ).substring(4);\n}\n\nconst currentTimestamp = props.timestamp ?? post.snapshot.timestamp;\nconst snapshot = post.snapshot;\nconst snapshotHistory = post.snapshot_history\n ? Array.from(post.snapshot_history)\n : [];\n\nsnapshotHistory.push(snapshot);\nsnapshotHistory.reverse();\n\nconst history = (\n <div class=\"btn-group\" role=\"group\">\n <a\n class=\"card-link\"\n role=\"button\"\n title=\"Post History\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n type=\"button\"\n >\n <div class=\"bi bi-clock-history px-2\"></div>\n </a>\n <ul class=\"dropdown-menu\">\n <a\n class=\"d-flex text-muted\"\n style={{ fontSize: \"12px\", textDecoration: \"none\", cursor: \"default\" }}\n >\n <a\n style={{\n textAlign: \"center\",\n minWidth: \"290px\",\n maxWidth: \"290px\",\n }}\n >\n Edit History\n </a>\n <a style={{ marginRight: \"8px\" }}>Compare</a>\n </a>\n {snapshotHistory.map((item) => {\n if (item === undefined) return;\n return (\n <li style={{ display: \"flex\" }}>\n <div\n style={{\n minWidth: \"290px\",\n maxWidth: \"290px\",\n }}\n >\n <a\n class=\"dropdown-item\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/devhub.entity.post.Post\",\n params: {\n id: postId,\n timestamp: item.timestamp,\n compareTimestamp: null,\n referral,\n },\n })}\n target={props.newTab ? \"_blank\" : undefined}\n >\n {readableDate(item.timestamp / 1000000)}\n\n <Widget\n src=\"mob.near/widget/ProfileImage\"\n props={{\n accountId: item.editor_id,\n style: {\n width: \"1.25em\",\n height: \"1.25em\",\n },\n imageStyle: {\n transform: \"translateY(-12.5%)\",\n },\n }}\n />\n {post.author_id.substring(0, 8)}\n </a>\n </div>\n <a\n class=\"dropdown-item\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/devhub.entity.post.Post\",\n params: {\n id: postId,\n timestamp: currentTimestamp,\n compareTimestamp: item.timestamp,\n referral,\n },\n })}\n >\n <i class=\"bi bi-file-earmark-diff\" />\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n);\n\nreturn history;\n" }, "devhub.entity.community.configuration.BrandingConfigurator": { "": "const Banner = styled.div`\n border-top-left-radius: var(--bs-border-radius-xl) !important;\n border-top-right-radius: var(--bs-border-radius-xl) !important;\n height: calc(100% - 100px);\n\n & > div :not(.btn) {\n position: absolute;\n display: none;\n margin: 0 !important;\n width: 0 !important;\n height: 0 !important;\n }\n\n .btn {\n padding: 0.5rem 0.75rem !important;\n min-height: 32;\n line-height: 1;\n\n border: none;\n border-radius: 50px;\n --bs-btn-color: #ffffff;\n --bs-btn-bg: #087990;\n --bs-btn-border-color: #087990;\n --bs-btn-hover-color: #ffffff;\n --bs-btn-hover-bg: #055160;\n --bs-btn-hover-border-color: #055160;\n --bs-btn-focus-shadow-rgb: 49, 132, 253;\n --bs-btn-active-color: #ffffff;\n --bs-btn-active-bg: #055160;\n --bs-btn-active-border-color: #055160;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n opacity: 0.8;\n\n &:hover {\n opacity: 1;\n }\n }\n`;\n\nconst Logo = styled.div`\n & > div :not(.btn) {\n position: absolute;\n display: none;\n margin: 0 !important;\n width: 0 !important;\n height: 0 !important;\n }\n\n .btn {\n padding: 0.5rem 0.75rem !important;\n min-height: 32;\n line-height: 1;\n\n border: none;\n border-radius: 50px;\n --bs-btn-color: #ffffff;\n --bs-btn-bg: #087990;\n --bs-btn-border-color: #087990;\n --bs-btn-hover-color: #ffffff;\n --bs-btn-hover-bg: #055160;\n --bs-btn-hover-border-color: #055160;\n --bs-btn-focus-shadow-rgb: 49, 132, 253;\n --bs-btn-active-color: #ffffff;\n --bs-btn-active-bg: #055160;\n --bs-btn-active-border-color: #055160;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n opacity: 0.8;\n\n &:hover {\n opacity: 1;\n }\n }\n`;\n\nconst cidToURL = (cid) => `https://ipfs.near.social/ipfs/${cid}`;\n\nconst { data, onSubmit, hasConfigurePermissions, link } = props;\n\nconst initialInput = { banner: null, logo: null };\n\nconst initialValues = {\n banner: { cid: data.banner_url.split(\"/\").at(-1) },\n logo: { cid: data.logo_url.split(\"/\").at(-1) },\n};\n\nState.init({\n input: initialInput,\n});\n\nconst hasUnsubmittedChanges = Object.values(state.input).some(\n (value) => value !== null\n);\n\nconst isSynced = state.input === initialValues;\n\nif (hasUnsubmittedChanges && !isSynced) {\n onSubmit({\n banner_url: cidToURL(state.input.banner?.cid ?? initialValues.banner.cid),\n logo_url: cidToURL(state.input.logo?.cid ?? initialValues.logo.cid),\n });\n\n State.update((lastKnownState) => ({\n ...lastKnownState,\n input: initialInput,\n }));\n}\n\nreturn (\n <div style={{ height: 280 }}>\n <Banner\n alt=\"Community banner preview\"\n className=\"card-img-top d-flex flex-column justify-content-end align-items-end p-4\"\n style={{\n background: `center / cover no-repeat url(${cidToURL(\n initialValues.banner.cid\n )})`,\n }}\n >\n {hasConfigurePermissions && (\n <IpfsImageUpload image={state.input.banner} />\n )}\n </Banner>\n <Logo\n alt=\"Community logo preview\"\n className={[\n \"d-flex flex-column justify-content-center align-items-center\",\n \"rounded-circle ms-5 border border-4 border-white\",\n ].join(\" \")}\n style={{\n marginTop: -64,\n width: 128,\n height: 128,\n\n background: `center / cover no-repeat url(${cidToURL(\n initialValues.logo.cid\n )})`,\n }}\n >\n {hasConfigurePermissions && <IpfsImageUpload image={state.input.logo} />}\n </Logo>\n\n <div\n className=\"card-body p-4\"\n style={{ marginTop: -64, marginLeft: 180, height: 84 }}\n >\n <h5\n className=\"h5 text-nowrap overflow-hidden\"\n style={{ textOverflow: \"ellipsis\" }}\n >\n {typeof link === \"string\" && link.length > 0 ? (\n <Link to={link}>{data.name}</Link>\n ) : (\n data.name\n )}\n </h5>\n\n <p\n className=\"card-text text-nowrap overflow-hidden\"\n style={{ textOverflow: \"ellipsis\" }}\n >\n {data.description}\n </p>\n </div>\n </div>\n);\n" }, "devhub.components.molecule.Tile": { "": "function Tile({ id, children, className, minHeight, style }) {\n return (\n <div\n id={id}\n className={[\n \"d-flex flex-column rounded-4 attractable w-100 border\",\n className,\n ].join(\" \")}\n style={{\n minHeight: minHeight ?? 180,\n height: \"fit-content\",\n ...style,\n }}\n >\n {children}\n </div>\n );\n}\n\nreturn { Tile };\n" }, "devhub.components.molecule.BadgeDetails": { "": "const nearDevGovBadgesContractId = \"neardevgov.near\";\n\nlet badgeId, ownerAccountId;\nif (props.tokenId) {\n let [_badgeId, _ownerAccountId] = props.tokenId.split(\":\", 2);\n badgeId = _badgeId;\n ownerAccountId = _ownerAccountId;\n} else {\n badgeId = props.badgeId;\n}\n\nif (!badgeId) {\n return (\n <>\n Please, provide <pre>badgeId</pre> or <pre>tokenId</pre> to the\n DevGovBadgeDetails component\n </>\n );\n}\n\nlet badgeMetadata =\n props.badgeMetadata ??\n Near.view(nearDevGovBadgesContractId, \"get_badge\", {\n badge_id: badgeId,\n }).badge_metadata;\n\nif (!badgeMetadata) {\n return <>Loading...</>;\n}\n\nreturn (\n <>\n <div className=\"bg-white shadow rounded overflow-hidden\">\n <div className=\"px-4 pt-0 pb-5 bg-dark position-relative\">\n <div\n className=\"profile-picture d-inline-block\"\n style={{ transform: \"translateY(7rem)\" }}\n >\n <Widget\n src=\"mob.near/widget/NftImage\"\n props={{\n style: { width: \"10em\", height: \"10em\" },\n className: \"rounded-circle w-100 h-100 img-thumbnail d-block\",\n nft: {\n tokenMetadata: badgeMetadata,\n contractId: nearDevGovBadgesContractId,\n },\n alt: badgeMetadata.title,\n }}\n />\n </div>\n </div>\n <div className=\"bg-light px-4 pb-4\">\n <div className=\"d-md-flex justify-content-between pt-3 mb-2\">\n <div style={{ paddingTop: \"3rem\" }}>\n <div className=\"me-2 d-sm-flex gap-1 flex-row align-items-center\">\n <div className=\"me-2 position-relative\">\n <h4 className=\"mt-0 mb-0 text-truncate\">\n {badgeMetadata.title}\n </h4>\n </div>\n </div>\n <div className=\"d-flex flex-row\">\n <div className=\"me-4\">\n <span className=\"text-muted\">Awarded to</span>\n <span className=\"fw-bolder\">{badgeMetadata.copies}</span>\n <span className=\"text-muted\">developers</span>\n </div>\n </div>\n <div>\n <Markdown text={badgeMetadata.description} />\n </div>\n </div>\n </div>\n </div>\n </div>\n </>\n);\n" }, "devhub.entity.community.configuration.InformationConfigurator": { "": "const CommunityInformationSchema = {\n name: {\n inputProps: {\n min: 2,\n max: 30,\n placeholder: \"Community name.\",\n required: true,\n },\n\n label: \"Name\",\n order: 1,\n },\n\n description: {\n inputProps: {\n min: 2,\n max: 60,\n\n placeholder:\n \"Describe your community in one short sentence that will appear in the communities discovery page.\",\n\n required: true,\n },\n\n label: \"Description\",\n order: 2,\n },\n\n handle: {\n inputProps: {\n min: 2,\n max: 40,\n allowCommaAndSpace: false,\n placeholder:\n \"Choose unique URL handle for your community. Example: zero-knowledge.\",\n\n required: true,\n },\n\n label: \"URL handle\",\n order: 3,\n },\n\n tag: {\n inputProps: {\n min: 2,\n max: 30,\n allowCommaAndSpace: false,\n placeholder:\n \"Any posts with this tag will show up in your community feed.\",\n\n required: true,\n },\n\n label: \"Tag\",\n order: 4,\n },\n};\n\nconst { data, onSubmit, onCancel, setIsActive, isActive } = props;\n\nfunction handleOnSubmit(v) {\n onSubmit(v);\n setIsActive(false);\n}\n\nreturn (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.organism.Configurator\"}\n props={{\n externalState: data,\n schema: CommunityInformationSchema,\n onSubmit: handleOnSubmit,\n isActive: isActive,\n onCancel: onCancel,\n }}\n />\n);\n" }, "devhub.entity.community.Tile": { "": "/* INCLUDE: \"core/lib/gui/attractable\" */\nconst AttractableDiv = styled.div`\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\nconst AttractableLink = styled.a`\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\nconst AttractableImage = styled.img`\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/* END_INCLUDE: \"core/lib/gui/attractable\" */\n\nconst Tile = ({\n children,\n borderRadius,\n className,\n fullWidth,\n headerSlotRight,\n heading,\n headingAdornment,\n id,\n isHidden,\n noBorder,\n noFrame,\n minHeight,\n style,\n}) => (\n <AttractableDiv\n className={[\n \"d-flex flex-column gap-3\",\n className,\n fullWidth ? \"w-100\" : \"\",\n !noFrame ? \"p-3\" : \"\",\n isHidden ? \"d-none\" : \"\",\n ].join(\" \")}\n style={{\n maxWidth: fullWidth ? \"100%\" : null,\n minHeight: minHeight ?? 180,\n height: \"fit-content\",\n overflowX: \"auto\",\n borderRadius: 16,\n border: \"1px solid rgba(129, 129, 129, 0.30)\",\n background: \"#FFFEFE\",\n marginBottom: \"1rem\",\n ...style,\n }}\n {...{ id }}\n >\n {\n <div\n className={[\n \"d-flex align-items-center justify-content-between gap-3\",\n\n noFrame || (!heading && !headingAdornment && !headerSlotRight)\n ? \"d-none\"\n : \"\",\n ].join(\" \")}\n style={{ minHeight: 30 }}\n >\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n {headingAdornment}\n <span>{heading}</span>\n </h5>\n\n {headerSlotRight}\n </div>\n }\n\n {children}\n </AttractableDiv>\n);\n\nreturn Tile(props);\n" }, "devhub.entity.proposal.ConfirmCancelModal": { "": "const isOpen = props.isOpen;\nconst onCancelClick = props.onCancelClick;\nconst onConfirmClick = props.onConfirmClick;\n\nconst Modal = styled.div`\n display: ${({ hidden }) => (hidden ? \"none\" : \"flex\")};\n position: fixed;\n inset: 0;\n justify-content: center;\n align-items: center;\n opacity: 1;\n z-index: 999;\n\n .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n\n @media screen and (max-width: 768px) {\n h5 {\n font-size: 16px !important;\n }\n }\n\n .btn {\n font-size: 14px;\n }\n`;\n\nconst ModalBackdrop = styled.div`\n position: absolute;\n inset: 0;\n background-color: rgba(0, 0, 0, 0.5);\n opacity: 0.4;\n`;\n\nconst ModalDialog = styled.div`\n padding: 2em;\n z-index: 999;\n overflow-y: auto;\n max-height: 85%;\n margin-top: 5%;\n width: 50%;\n\n @media screen and (max-width: 768px) {\n margin: 2rem;\n width: 100%;\n }\n`;\n\nconst ModalHeader = styled.div`\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding-bottom: 4px;\n`;\n\nconst ModalFooter = styled.div`\n padding-top: 4px;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: items-center;\n`;\n\nconst CloseButton = styled.button`\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: white;\n padding: 0.5em;\n border-radius: 6px;\n border: 0;\n color: #344054;\n\n &:hover {\n background-color: #d3d3d3;\n }\n`;\n\nconst ConfirmButton = styled.button`\n padding: 0.7em;\n border-radius: 6px;\n border: 0;\n box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);\n background-color: #12b76a;\n color: white;\n\n &:hover {\n background-color: #0e9f5d;\n }\n`;\n\nconst ModalContent = styled.div`\n flex: 1;\n font-size: 14px;\n margin-top: 4px;\n margin-bottom: 4px;\n overflow-y: auto;\n max-height: 50%;\n\n @media screen and (max-width: 768px) {\n font-size: 12px !important;\n }\n`;\n\nconst NoButton = styled.button`\n background: transparent;\n border: none;\n padding: 0;\n margin: 0;\n box-shadow: none;\n`;\n\nreturn (\n <>\n <Modal hidden={!isOpen}>\n <ModalBackdrop />\n <ModalDialog className=\"card\">\n <ModalHeader>\n <h5 className=\"mb-0\">Confirm proposal cancellation</h5>\n </ModalHeader>\n <ModalContent>\n If you cancel this proposal, the status will change to Cancelled and\n indicate to sponsors that this proposal is no longer active or\n relevant. Comments are still open, but you cannot reopen this proposal\n or make additional changes.\n <br /> Are you sure you want to proceed?\n </ModalContent>\n <div className=\"d-flex gap-2 align-items-center justify-content-end mt-2\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-secondary\" },\n label: \"Cancel\",\n onClick: onCancelClick,\n }}\n />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-danger\" },\n label: \"Ready to Cancel\",\n onClick: onConfirmClick,\n }}\n />\n </div>\n </ModalDialog>\n </Modal>\n </>\n);\n" }, "devhub.components.organism.Configurator": { "": "const Struct = VM.require(\"devhub.megha19.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.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"\n }\n props={{\n text: fieldValue,\n }}\n />\n ) : (\n <span>none</span>\n )}\n </ValueView>\n </div>\n <Widget\n src={`devhub.megha19.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.megha19.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.megha19.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" }, "devhub.components.templates.AppLayout": { "": "const Theme = styled.div`\n inset: 73px 0px 0px;\n width: 100%;\n display: flex;\n flex-direction: column;\n overflow-y: scroll;\n padding-top: calc(-1 * var(--body-top-padding));\n background: #f4f4f4;\n .container-xl {\n padding-inline: 0px !important;\n }\n`;\n\nconst Container = styled.div`\n width: 100%;\n`;\n\nconst ContentContainer = styled.div`\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n width: 100%;\n`;\n\nconst AppHeader = ({ page }) => (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.organism.Navbar\"\n props={{\n page: page,\n ...props,\n }}\n />\n);\n\nconst Footer = (props) => {\n return (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.organism.NewsLetter\"\n props={{\n ...props,\n }}\n />\n );\n};\n\nfunction AppLayout({ page, children }) {\n return (\n <Theme>\n <Container className=\"container-xl\">\n <AppHeader page={page} />\n <ContentContainer>{children}</ContentContainer>\n <Footer page={page} />\n </Container>\n </Theme>\n );\n}\n\nreturn { AppLayout };\n" }, "devhub.components.molecule.MarkdownViewer": { "": "const Wrapper = styled.div`\n p {\n white-space: pre-line; // This ensures text breaks to new line\n\n span {\n white-space: normal; // and this ensures profile links look normal\n }\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 pre {\n background-color: #f4f4f4;\n border: 1px solid #ddd;\n border-radius: 4px;\n padding: 1em;\n overflow-x: auto;\n font-family: \"Courier New\", Courier, monospace;\n }\n\n a {\n color: #04a46e;\n }\n`;\n\nconst Embedded = styled.span`\n white-space: normal;\n\n p {\n white-space: normal;\n }\n`;\n\nconst renderMention =\n props.renderMention ??\n ((accountId) => (\n <span key={accountId} className=\"d-inline-flex\" style={{ fontWeight: 500 }}>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.ProfileLine\"\n props={{\n accountId: accountId.toLowerCase(),\n hideAccountId: true,\n tooltip: true,\n }}\n />\n </span>\n ));\n\nreturn (\n <Wrapper>\n <Markdown text={props.text} onMention={renderMention} />\n </Wrapper>\n);\n" }, "devhub.entity.community.Spawner": { "": "const { typeMatch } = VM.require(\"devhub.megha19.near/widget/core.lib.struct\");\n\nif (!typeMatch) {\n return <p>Loading modules...</p>;\n}\n\nconst { data, onSubmit, onCancel } = props;\n\nconst CommunityInputsPartialSchema = {\n handle: {\n inputProps: {\n min: 2,\n max: 40,\n allowCommaAndSpace: false,\n placeholder:\n \"Choose unique URL handle for your community. Example: zero-knowledge.\",\n required: true,\n },\n\n label: \"URL handle\",\n order: 3,\n },\n\n name: {\n inputProps: {\n min: 2,\n max: 30,\n placeholder: \"Community name.\",\n required: true,\n },\n\n label: \"Name\",\n order: 1,\n },\n\n tag: {\n inputProps: {\n min: 2,\n max: 30,\n allowCommaAndSpace: false,\n placeholder:\n \"Any posts with this tag will show up in your community feed.\",\n\n required: true,\n },\n\n label: \"Tag\",\n order: 4,\n },\n\n description: {\n inputProps: {\n min: 2,\n max: 60,\n\n placeholder:\n \"Describe your community in one short sentence that will appear in the communities discovery page.\",\n\n required: true,\n },\n\n label: \"Description\",\n order: 2,\n },\n};\n\nconst communityInputsValidator = (formValues) =>\n typeMatch(formValues) &&\n Object.values(formValues).every(\n (value) => typeof value === \"string\" && value.length > 0\n );\n\nconst CommunityInputsDefaults = {\n handle: \"\",\n name: \"\",\n tag: \"\",\n description: \"\",\n};\n\nreturn (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.organism.Configurator\"\n props={{\n heading: \"Community information\",\n externalState: CommunityInputsDefaults,\n fullWidth: true,\n isActive: true,\n isUnlocked: true,\n isValid: communityInputsValidator,\n onSubmit: onSubmit,\n schema: CommunityInputsPartialSchema,\n submitIcon: {\n type: \"bootstrap_icon\",\n variant: \"bi-rocket-takeoff-fill\",\n },\n submitLabel: \"Launch\",\n onCancel: onCancel,\n }}\n />\n);\n" }, "core.lib.stringUtils": { "": "/**\n * Transform input into a consistent and standardized format\n *\n * @param {string} text - The input to normalize.\n * @returns {string} - normalized input\n */\n\nconst normalize = (text) =>\n text\n .replaceAll(/[- \\.]/g, \"_\")\n .replaceAll(/[^\\w]+/g, \"\")\n .replaceAll(/_+/g, \"-\")\n .replace(/^-+/, \"\")\n .replace(/-+$/, \"\")\n .toLowerCase()\n .trim(\"-\");\n\nreturn { normalize };\n" }, "devhub.components.molecule.Switch": { "": "const { className, currentValue, isHidden, key, onChange, options, title } =\n props;\n\nreturn (\n <div\n className={[\n \"btn-group shadow\",\n className ?? \"\",\n isHidden ?? false ? \"d-none\" : \"\",\n ].join(\" \")}\n role=\"group\"\n aria-label={title}\n key={`${key}-${value}`}\n {...{ title }}\n >\n {options.map(({ label, value }) => (\n <>\n <input\n checked={currentValue === value}\n className=\"btn-check\"\n id={`${key}-${value}`}\n name={`${key}-${value}`}\n type=\"radio\"\n {...{ onChange, value }}\n />\n\n <label\n className={[\n \"btn btn-sm\",\n currentValue === value ? \"btn-dark\" : \"btn-light\",\n ].join(\" \")}\n for={`${key}-${value}`}\n >\n {label}\n </label>\n </>\n ))}\n </div>\n);\n" }, "devhub.entity.addon.blog.editor.preview": { "": "return (\n <div>\n <h1>Preview</h1>\n <p>{JSON.stringify(props)}</p>\n </div>\n);\n" }, "devhub.components.island.hero": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nhref || (href = () => {});\n\nconst imageLink =\n \"https://ipfs.near.social/ipfs/bafybeiap2mzwsly4apaldxguiunx4rjwqyadksj5yxuzwrww3kue3ao5qe\";\n\nconst HeroSection = styled.div`\n position: relative;\n height: auto;\n z-index: 3;\n width: 70%;\n background: #00ec97;\n clip-path: polygon(0 0, 100% 0%, 75% 100%, 0% 100%);\n\n padding-top: 2rem;\n padding-bottom: 2rem;\n padding-left: 3.375rem;\n\n @media screen and (max-width: 768px) {\n width: 100%;\n padding: 1rem 1.5rem;\n clip-path: none;\n }\n`;\n\nconst Title = styled.h1`\n color: #f4f4f4;\n font-size: 4rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 88px */\n letter-spacing: -1.76px;\n\n @media screen and (max-width: 768px) {\n font-size: 2.25rem;\n letter-spacing: -0.72px;\n margin: 0;\n }\n`;\n\nconst Lead = styled.p`\n color: #151515;\n font-size: 1.75rem;\n font-style: normal;\n font-weight: 400;\n line-height: 110%; /* 39.6px */\n\n width: 70%;\n\n @media screen and (max-width: 768px) {\n font-size: 1.5rem;\n width: 100%;\n }\n`;\n\nconst CTA = styled.a`\n display: inline-flex;\n padding: 0.875rem 1rem;\n align-items: center;\n gap: 0.5rem;\n\n border-radius: 1rem;\n border: 1px solid #151515;\n\n color: #151515 !important;\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.48px;\n\n &:hover {\n background: #151515;\n color: #f4f4f4 !important;\n text-decoration: none; // Remove underline on hover\n }\n\n @media screen and (max-width: 768px) {\n display: inline-flex;\n padding: 8px 16px;\n align-items: center;\n gap: 8px;\n\n border-radius: 16px;\n background: #00ec97;\n\n border: none;\n\n color: #f4f4f4 !important;\n font-size: 16px;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 19.2px */\n letter-spacing: -0.32px;\n\n &:hover {\n background: #151515;\n color: #f4f4f4;\n text-decoration: none; // Remove underline on hover\n }\n }\n`;\n\nconst Container = styled.div`\n position: relative;\n width: 100%;\n height: max-content;\n overflow: hidden;\n\n @media screen and (max-width: 768px) {\n background: #f4f4f4;\n }\n`;\n\nconst ImageContainer = styled.div`\n width: 100%;\n height: 100%;\n position: absolute;\n top: 0;\n right: 0;\n z-index: 1;\n background: transparent;\n\n @media screen and (max-width: 768px) {\n display: none;\n }\n`;\n\nconst Image = styled.img`\n margin-left: 15.625rem;\n height: 100%;\n width: 100%;\n filter: grayscale(100%);\n object-fit: cover;\n`;\n\nconst DesktopDescription = styled.div`\n @media screen and (max-width: 786px) {\n display: none;\n }\n`;\n\nconst MobileImage = styled.img`\n display: none;\n\n width: 100%;\n height: 196px;\n\n width: 100%;\n object-fit: cover;\n filter: grayscale(1);\n\n @media screen and (max-width: 768px) {\n display: block;\n }\n`;\n\nconst MobileDescription = styled.div`\n display: none;\n padding: 24px 16px;\n\n width: 100%;\n\n @media screen and (max-width: 768px) {\n display: block;\n }\n`;\n\nreturn (\n <Container>\n <HeroSection>\n <Title>\n The decentralized <br />\n <span style={{ color: \"#101820\" }}>home base</span> <br />\n for NEAR builders\n </Title>\n <DesktopDescription>\n <Lead>\n Join a vibrant community of innovators shaping the open web.\n </Lead>\n <Link\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"about\",\n },\n })}\n >\n <CTA href=\"#\">Read more →</CTA>\n </Link>\n </DesktopDescription>\n </HeroSection>\n <MobileImage src={imageLink} />\n <ImageContainer>\n <Image src={imageLink} />\n </ImageContainer>\n <MobileDescription>\n <Lead>Join a vibrant community of innovators shaping the open web.</Lead>\n <Link\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"about\",\n },\n })}\n >\n <CTA href=\"#\">Read more →</CTA>\n </Link>\n </MobileDescription>\n </Container>\n);\n" }, "devhub.entity.post.Panel": { "": "//////////////////////////////////////////////////////////////////////\n///STOPWORDS//////////////////////////////////////////////////////////\nconst stopWords = [\n \"about\",\n \"above\",\n \"after\",\n \"again\",\n \"against\",\n \"all\",\n \"and\",\n \"any\",\n \"are\",\n \"because\",\n \"been\",\n \"before\",\n \"being\",\n \"below\",\n \"between\",\n \"both\",\n \"but\",\n \"can\",\n \"cannot\",\n \"could\",\n \"did\",\n \"does\",\n \"doing\",\n \"down\",\n \"during\",\n \"each\",\n \"etc\",\n \"few\",\n \"for\",\n \"from\",\n \"further\",\n \"had\",\n \"has\",\n \"have\",\n \"having\",\n \"her\",\n \"hers\",\n \"herself\",\n \"him\",\n \"himself\",\n \"his\",\n \"how\",\n \"into\",\n \"its\",\n \"itself\",\n \"just\",\n \"more\",\n \"most\",\n \"myself\",\n \"nor\",\n \"not\",\n \"now\",\n \"off\",\n \"once\",\n \"only\",\n \"other\",\n \"our\",\n \"ours\",\n \"ourselves\",\n \"out\",\n \"over\",\n \"own\",\n \"same\",\n \"she\",\n \"should\",\n \"some\",\n \"still\",\n \"such\",\n \"than\",\n \"that\",\n \"the\",\n \"their\",\n \"theirs\",\n \"them\",\n \"themselves\",\n \"then\",\n \"there\",\n \"these\",\n \"they\",\n \"this\",\n \"those\",\n \"through\",\n \"too\",\n \"under\",\n \"until\",\n \"very\",\n \"was\",\n \"were\",\n \"what\",\n \"when\",\n \"where\",\n \"which\",\n \"while\",\n \"who\",\n \"whom\",\n \"why\",\n \"will\",\n \"with\",\n \"you\",\n \"your\",\n \"yours\",\n \"yourself\",\n \"yourselves\",\n \"www\",\n \"http\",\n \"com\",\n];\n\nconst stopWordsDictionary = {};\nfor (let i = 0; i < stopWords.length; i++) {\n stopWordsDictionary[stopWords[i]] = true;\n}\n\nfunction isStopWord(word) {\n return stopWordsDictionary.hasOwnProperty(word.toLowerCase());\n}\n//////////////////////////////////////////////////////////////////////\n///SYNONYMS///////////////////////////////////////////////////////////\nconst synonyms = {\n ether: \"ethereum\",\n eth: \"ethereum\",\n either: \"ethereum\",\n app: \"application\",\n cryptocyrrency: \"crypto\",\n developerdao: \"devdao\",\n dev: \"develop\",\n doc: \"document\",\n lib: \"librari\",\n saw: \"see\",\n seen: \"see\",\n tweet: \"twitter\",\n paid: \"pai\",\n src: \"sourc\",\n};\n\nconst applySynonym = (word) => {\n if (synonyms.hasOwnProperty(word.toLowerCase())) {\n return synonyms[word];\n }\n return word;\n};\n//////////////////////////////////////////////////////////////////////\n///STEMMING///////////////////////////////////////////////////////////\nconst step2list = {\n ational: \"ate\",\n tional: \"tion\",\n enci: \"ence\",\n anci: \"ance\",\n izer: \"ize\",\n bli: \"ble\",\n alli: \"al\",\n entli: \"ent\",\n eli: \"e\",\n ousli: \"ous\",\n ization: \"ize\",\n ation: \"ate\",\n ator: \"ate\",\n alism: \"al\",\n iveness: \"ive\",\n fulness: \"ful\",\n ousness: \"ous\",\n aliti: \"al\",\n iviti: \"ive\",\n biliti: \"ble\",\n logi: \"log\",\n};\n\n/** @type {Record<string, string>} */\nconst step3list = {\n icate: \"ic\",\n ative: \"\",\n alize: \"al\",\n iciti: \"ic\",\n ical: \"ic\",\n ful: \"\",\n ness: \"\",\n};\n\nconst gt0 = /^([^aeiou][^aeiouy]*)?([aeiouy][aeiou]*)([^aeiou][^aeiouy]*)/;\nconst eq1 =\n /^([^aeiou][^aeiouy]*)?([aeiouy][aeiou]*)([^aeiou][^aeiouy]*)([aeiouy][aeiou]*)?$/;\nconst gt1 =\n /^([^aeiou][^aeiouy]*)?(([aeiouy][aeiou]*)([^aeiou][^aeiouy]*)){2,}/;\nconst vowelInStem = /^([^aeiou][^aeiouy]*)?[aeiouy]/;\nconst consonantLike = /^([^aeiou][^aeiouy]*)[aeiouy][^aeiouwxy]$/;\n\n// Exception expressions.\nconst sfxLl = /ll$/;\nconst sfxE = /^(.+?)e$/;\nconst sfxY = /^(.+?)y$/;\nconst sfxIon = /^(.+?(s|t))(ion)$/;\nconst sfxEdOrIng = /^(.+?)(ed|ing)$/;\nconst sfxAtOrBlOrIz = /(at|bl|iz)$/;\nconst sfxEED = /^(.+?)eed$/;\nconst sfxS = /^.+?[^s]s$/;\nconst sfxSsesOrIes = /^.+?(ss|i)es$/;\nconst sfxMultiConsonantLike = /([^aeiouylsz])\\1$/;\nconst step2 =\n /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;\nconst step3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;\nconst step4 =\n /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;\n\n/**\n * Get the stem from a given value.\n *\n * @param {string} value\n * Value to stem.\n * @returns {string}\n * Stem for `value`\n */\n// eslint-disable-next-line complexity\nfunction stemmer(value) {\n let result = value.toLowerCase();\n\n // Exit early.\n if (result.length < 3) {\n return result;\n }\n\n /** @type {boolean} */\n let firstCharacterWasLowerCaseY = false;\n\n // Detect initial `y`, make sure it never matches.\n if (\n result.codePointAt(0) === 121 // Lowercase Y\n ) {\n firstCharacterWasLowerCaseY = true;\n result = \"Y\" + result.slice(1);\n }\n\n // Step 1a.\n if (sfxSsesOrIes.test(result)) {\n // Remove last two characters.\n result = result.slice(0, -2);\n } else if (sfxS.test(result)) {\n // Remove last character.\n result = result.slice(0, -1);\n }\n\n /** @type {RegExpMatchArray|null} */\n let match;\n\n // Step 1b.\n if ((match = sfxEED.exec(result))) {\n if (gt0.test(match[1])) {\n // Remove last character.\n result = result.slice(0, -1);\n }\n } else if ((match = sfxEdOrIng.exec(result)) && vowelInStem.test(match[1])) {\n result = match[1];\n\n if (sfxAtOrBlOrIz.test(result)) {\n // Append `e`.\n result += \"e\";\n } else if (sfxMultiConsonantLike.test(result)) {\n // Remove last character.\n result = result.slice(0, -1);\n } else if (consonantLike.test(result)) {\n // Append `e`.\n result += \"e\";\n }\n }\n\n // Step 1c.\n if ((match = sfxY.exec(result)) && vowelInStem.test(match[1])) {\n // Remove suffixing `y` and append `i`.\n result = match[1] + \"i\";\n }\n\n // Step 2.\n if ((match = step2.exec(result)) && gt0.test(match[1])) {\n result = match[1] + step2list[match[2]];\n }\n\n // Step 3.\n if ((match = step3.exec(result)) && gt0.test(match[1])) {\n result = match[1] + step3list[match[2]];\n }\n\n // Step 4.\n if ((match = step4.exec(result))) {\n if (gt1.test(match[1])) {\n result = match[1];\n }\n } else if ((match = sfxIon.exec(result)) && gt1.test(match[1])) {\n result = match[1];\n }\n\n // Step 5.\n if (\n (match = sfxE.exec(result)) &&\n (gt1.test(match[1]) ||\n (eq1.test(match[1]) && !consonantLike.test(match[1])))\n ) {\n result = match[1];\n }\n\n if (sfxLl.test(result) && gt1.test(result)) {\n result = result.slice(0, -1);\n }\n\n // Turn initial `Y` back to `y`.\n if (firstCharacterWasLowerCaseY) {\n result = \"y\" + result.slice(1);\n }\n\n return result;\n}\n\n//////////////////////////////////////////////////////////////////////\n///SPELLCHECK/////////////////////////////////////////////////////////\nfunction levenshteinDistance(s, t, threshold) {\n const BIG_NUMBER = 10000;\n if (s == null || t == null) {\n return BIG_NUMBER;\n }\n if (threshold < 0) {\n return BIG_NUMBER;\n }\n let n = s.length;\n let m = t.length;\n if (Math.abs(n - m) >= threshold) {\n return BIG_NUMBER;\n }\n\n // if one string is empty, the edit distance is necessarily the length of the other\n if (n == 0) {\n return m <= threshold ? m : BIG_NUMBER;\n } else if (m == 0) {\n return n <= threshold ? n : BIG_NUMBER;\n }\n\n if (n > m) {\n // swap the two strings to consume less memory\n let temp = s;\n s = t;\n t = temp;\n let tempSize = n;\n n = m;\n m = tempSize;\n }\n\n let p = Array.from({ length: n + 1 }, () => 0); // 'previous' cost array, horizontally\n let d = Array.from({ length: n + 1 }, () => 0); // cost array, horizontally\n let _d; // placeholder to assist in swapping p and d\n\n // fill in starting table values\n const boundary = Math.min(n, threshold) + 1;\n for (let i = 0; i < boundary; i++) {\n p[i] = i;\n }\n // these fills ensure that the value above the rightmost entry of our\n // stripe will be ignored in following loop iterations\n for (let i = boundary; i < p.length; i++) {\n p[i] = BIG_NUMBER;\n }\n for (let i = 0; i < d.length; i++) {\n d[i] = BIG_NUMBER;\n }\n\n // iterates through t\n for (let j = 1; j <= m; j++) {\n const t_j = t.charAt(j - 1); // jth character of t\n d[0] = j;\n\n // compute stripe indices, constrain to array size\n const min = Math.max(1, j - threshold);\n const max = j > BIG_NUMBER - threshold ? n : Math.min(n, j + threshold);\n\n // the stripe may lead off of the table if s and t are of different sizes\n if (min > max) {\n return BIG_NUMBER;\n }\n\n // ignore entry left of leftmost\n if (min > 1) {\n d[min - 1] = BIG_NUMBER;\n }\n\n // iterates through [min, max] in s\n for (let i = min; i <= max; i++) {\n if (s.charAt(i - 1) == t_j) {\n // diagonally left and up\n d[i] = p[i - 1];\n } else {\n // 1 + minimum of cell to the left, to the top, diagonally left and up\n d[i] = 1 + Math.min(Math.min(d[i - 1], p[i]), p[i - 1]);\n }\n }\n\n // copy current distance counts to 'previous row' distance counts\n _d = p;\n p = d;\n d = _d;\n }\n // we don't need to check for threshold here because we did it inside the loop\n return p[n] <= threshold ? p[n] : BIG_NUMBER;\n}\n\nconst spellcheckQueryProcessing = (query, dictionary) => {\n // Split text document into words\n const words = stemAndFilterQuery(query);\n const dictionaryArray = Object.keys(dictionary);\n // Iterate over each word in the text\n for (let i = 0; i < words.length; i++) {\n let word = words[i].toLowerCase().replace(/[^a-z0-9]/g, \"\");\n\n // If the word is not in the dictionary, find the closest match\n if (!dictionary.hasOwnProperty(word)) {\n let closestMatch = undefined;\n let closestDistance = word.length;\n let allowedDistance = Math.min(word.length - 1, 3);\n // Iterate over each word in the dictionary\n if (word.length > 1) {\n for (let j = 0; j < dictionaryArray.length; j++) {\n let dictWord = dictionaryArray[j];\n let distance = levenshteinDistance(word, dictWord, allowedDistance);\n\n // If the distance is less than the closest distance, update the closest match\n if (distance <= allowedDistance && distance < closestDistance) {\n closestMatch = dictWord;\n closestDistance = distance;\n }\n }\n }\n // Replace the misspelled word with the closest match\n words[i] = closestMatch;\n }\n }\n return words.filter((word) => !!word);\n};\n\n//////////////////////////////////////////////////////////////////////\n///INDEXER&SEARCH/////////////////////////////////////////////////////\nconst fillDictionaryWith = (dict, text, id) => {\n let word = \"\";\n for (let i = 0; i < text.length; i++) {\n const char = text.charAt(i);\n const nextChar = text.charAt(i + 1);\n if (/\\w/.test(char) || (char === \".\" && /\\w/.test(nextChar))) {\n word += char.toLowerCase();\n } else if (word.length > 0) {\n const processedWord = applySynonym(stemmer(word));\n if (processedWord.length > 1 && !isStopWord(processedWord)) {\n const oldValue = dict[processedWord] || [];\n dict[processedWord] = [...oldValue, id];\n }\n word = \"\";\n }\n }\n const processedWord = applySynonym(stemmer(word));\n if (processedWord.length > 1 && !isStopWord(processedWord)) {\n const oldValue = dict[stemmer(processedWord)] || [];\n dict[stemmer(processedWord)] = [...oldValue, id];\n }\n return dict;\n};\n\nconst buildIndex = (posts) => {\n let index = {};\n\n posts.forEach((post) => {\n const title = post.snapshot.name;\n const labels = post.snapshot.labels.join(\" \");\n const text = post.snapshot.description;\n const postType = post.snapshot.post_type;\n const authorId = post.author_id;\n const postText = `${authorId} ${postType} ${title} ${labels} ${text}`;\n index = fillDictionaryWith(index, postText, post.id);\n });\n return index;\n};\n\nconst stemAndFilterQuery = (query) => {\n return Object.keys(fillDictionaryWith({}, query));\n};\n\nconst sortSearchResult = (searchResult) => {\n // create a map to count the frequency of each element\n const freq = new Map();\n for (const num of searchResult) {\n freq.set(num, (freq.get(num) || 0) + 1);\n }\n\n // define a custom comparison function to sort the array\n function compare(a, b) {\n // compare the frequency of the two elements\n const freqDiff = freq.get(b) - freq.get(a);\n if (freqDiff !== 0) {\n return freqDiff; // if they have different frequency, sort by frequency\n } else {\n return 0; // if they have the same frequency, leave as it is. Will be sorted by search term, by date\n }\n }\n\n // sort the array using the custom comparison function\n searchResult.sort(compare);\n return searchResult.filter(\n (elem, index) => searchResult.indexOf(elem) === index\n );\n};\n\nconst search = (processedQueryArray, index) => {\n return sortSearchResult(\n processedQueryArray.flatMap((queryWord) => {\n const termSearchRes = index[queryWord].reverse();\n const termSortedSearchRes = sortSearchResult(termSearchRes);\n return termSortedSearchRes;\n })\n );\n};\n\n//////////////////////////////////////////////////////////////////////\n///UI&UX//////////////////////////////////////////////////////////////\n//Run search and spelling computation every time the search bar modified\n//but no more frequent than 1 time per 1.5 seconds\nconst amountOfResultsToShowFirst = 5;\n\nconst buildPostsIndex = () => {\n return Near.asyncView(\"devgovgigs.near\", \"get_posts\").then((posts) => {\n const index = buildIndex(posts);\n const data = posts.reduce((acc, post) => {\n acc[post.id] = post;\n return acc;\n }, {});\n return { index, data };\n });\n};\n\nconst getProcessedPostsCached = () => {\n return useCache(() => buildPostsIndex(), \"processedPostsCached\");\n};\n\nif (!state.interval) {\n let termStorage = \"\";\n Storage.privateSet(\"term\", \"\");\n setInterval(() => {\n const currentInput = Storage.privateGet(\"term\");\n if (currentInput !== termStorage) {\n termStorage = currentInput;\n computeResults(termStorage);\n }\n }, 1500);\n State.update({\n interval: true,\n });\n}\n\nconst computeResults = (term) => {\n const start = new Date().getTime();\n const processedPostsCached = useCache(\n () =>\n buildPostsIndex().then((processedPosts) => {\n // Run query first time posts retrieved\n const query = term;\n const processedQuery = spellcheckQueryProcessing(\n query,\n processedPosts.index\n );\n const searchResult = search(processedQuery, processedPosts.index);\n console.log(processedQuery);\n console.log(searchResult);\n State.update({\n searchResult,\n shownSearchResults: searchResult.slice(0, amountOfResultsToShowFirst),\n processedQuery,\n loading: false,\n });\n return processedPosts;\n }),\n \"processedPostsCached\"\n );\n if (processedPostsCached) {\n // Run query every other time after data retrieved and cached\n const query = term;\n const processedQuery = spellcheckQueryProcessing(\n query,\n processedPostsCached.index\n );\n const searchResult = search(processedQuery, processedPostsCached.index);\n console.log(processedQuery);\n console.log(searchResult);\n State.update({\n searchResult,\n shownSearchResults: searchResult.slice(0, 10),\n processedQuery,\n loading: false,\n });\n }\n const end = new Date().getTime();\n console.log(\"search time: \", end - start);\n};\n\nconst updateInput = (term) => {\n Storage.privateSet(\"term\", term);\n State.update({\n term,\n loading: true,\n });\n};\n\nconst getSearchResultsKeywordsFor = (postId) => {\n const index = getProcessedPostsCached().index;\n return state.processedQuery.filter((queryWord) => {\n return index[queryWord].includes(postId);\n });\n};\n\nconst showMoreSearchResults = () => {\n const shownSearchResults = state.shownSearchResults || [];\n const newShownSearchResults = state.searchResult.slice(\n 0,\n shownSearchResults.length + amountOfResultsToShowFirst\n );\n State.update({ shownSearchResults: newShownSearchResults });\n};\n\nreturn (\n <>\n <div className=\"d-flex flex-row gap-4\">\n <div className=\"d-flex flex-row position-relative w-25\">\n <div className=\"position-absolute d-flex ps-3 flex-column h-100 justify-center\">\n {state.loading ? (\n <span\n className=\"spinner-grow spinner-grow-sm m-auto\"\n role=\"status\"\n aria-hidden=\"true\"\n />\n ) : (\n <i class=\"bi bi-search m-auto\"></i>\n )}\n </div>\n <input\n type=\"search\"\n className=\"ps-5 form-control border border-0 bg-light\"\n value={state.term ?? \"\"}\n onChange={(e) => updateInput(e.target.value)}\n placeholder={props.placeholder ?? `Search Posts`}\n />\n </div>\n <div class=\"dropdown\">\n <button\n class=\"btn btn-light dropdown-toggle\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n >\n Sort\n </button>\n <ul class=\"dropdown-menu px-2 shadow\">\n <li>\n <a\n style={{ borderRadius: \"5px\" }}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n >\n Latest\n </a>\n </li>\n <li>\n <a\n style={{ borderRadius: \"5px\" }}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n >\n Hottest\n </a>\n </li>\n <li>\n <a\n style={{ borderRadius: \"5px\" }}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n >\n All replies\n </a>\n </li>\n </ul>\n </div>\n <div class=\"dropdown\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.feature.post-search.by-author\"\n props={{\n authorQuery: props.authorQuery,\n onAuthorSearch: props.onAuthorSearch,\n }}\n />\n </div>\n <div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.feature.post-search.by-tag\"\n props={{\n tagQuery: props.tagQuery,\n onTagSearch: props.onTagSearch,\n }}\n />\n </div>\n <div className=\"d-flex flex-row-reverse flex-grow-1\">\n {props.children}\n </div>\n </div>\n {state.processedQuery &&\n state.processedQuery.length > 0 &&\n state.term.toLowerCase().trim() !== state.processedQuery.join(\" \") && (\n <div class=\"mb-2\" style={{ \"font-family\": \"monospace\" }}>\n Looking for\n <strong>{state.processedQuery.join(\" \")}</strong>:\n </div>\n )}\n {state.term && state.term.length > 1 && state.searchResult ? (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.post.List\"}\n props={{\n searchResult: {\n postIds: state.searchResult,\n keywords: Object.fromEntries(\n state.searchResult.map((postId) => {\n return [postId, getSearchResultsKeywordsFor(postId)];\n })\n ),\n },\n recency: props.recency,\n tag: props.tag,\n author: props.author,\n }}\n key={key}\n />\n ) : (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.post.List\"}\n props={{\n recency: props.recency,\n tag: props.tag,\n author: props.author,\n transactionHashes: props.transactionHashes,\n }}\n key={key}\n />\n )}\n </>\n);\n" }, "devhub.components.organism.Navbar": { "": "const page = props.page;\n\nconst [showMenu, setShowMenu] = useState(false);\n\nconst { href: linkHref } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nconst { hasModerator } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nlinkHref || (linkHref = () => {});\n\nconst Logo = () => {\n const Wrapper = styled.div`\n @media screen and (max-width: 768px) {\n svg {\n width: 90px;\n height: 12px;\n transform: scale(1.5);\n margin-left: 1rem;\n }\n }\n `;\n\n return (\n <Wrapper>\n <Link\n to={linkHref({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"home\" },\n })}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"180\"\n height=\"24\"\n viewBox=\"0 0 180 24\"\n fill=\"none\"\n >\n <g clip-path=\"url(#clip0_530_29)\">\n <path\n d=\"M8.62185 6.09766C11.8428 6.09766 14.5995 7.77588 14.5995 12.7821V20.8888H10.508V13.1803C10.508 10.7057 9.55041 9.39721 7.49016 9.39721C5.37187 9.39721 4.15313 10.8763 4.15313 13.4079V20.8888H0.0616455V6.26832H3.63081L4.00804 8.08877C4.96563 6.95099 6.32945 6.09766 8.62185 6.09766ZM23.911 21.0594C18.9489 21.0594 15.9601 18.1297 15.9601 13.607C15.9601 9.05588 19.007 6.09766 23.6208 6.09766C28.0895 6.09766 31.1073 8.82832 31.1654 13.1234C31.1654 13.5501 31.1364 14.0337 31.0493 14.4888H20.2257V14.6879C20.3128 16.7643 21.6766 18.0159 23.7369 18.0159C25.3909 18.0159 26.5516 17.3048 26.8998 15.9394H30.9332C30.4689 18.7839 27.8864 21.0594 23.911 21.0594ZM20.3128 11.8719H27.0449C26.7547 10.0799 25.5069 9.08432 23.6498 9.08432C21.8797 9.08432 20.5449 10.1368 20.3128 11.8719ZM47.0396 17.5039H47.5039V20.8888H45.4146C43.2963 20.8888 42.6289 19.8932 42.6579 18.4994C41.6133 20.1208 40.1044 21.0594 37.783 21.0594C34.562 21.0594 32.2406 19.5519 32.2406 16.7643C32.2406 13.6639 34.6201 11.9003 39.0888 11.9003H42.0486V11.1892C42.0486 9.88077 41.091 9.02743 39.3789 9.02743C37.783 9.02743 36.7093 9.73854 36.5352 10.8194H32.5888C32.879 7.97499 35.5486 6.09766 39.495 6.09766C43.6736 6.09766 46.082 7.9181 46.082 11.4168V16.5937C46.082 17.3617 46.4012 17.5039 47.0396 17.5039ZM42.0486 14.8585V14.5741H39.0598C37.3477 14.5741 36.3611 15.2568 36.3611 16.4799C36.3611 17.5039 37.2026 18.1581 38.5665 18.1581C40.7138 18.1581 42.0196 16.8497 42.0486 14.8585ZM56.8924 6.26832H57.5889V9.90921H55.9639C53.5264 9.90921 52.5978 11.5021 52.5978 13.7208V20.8888H48.5064V6.26832H52.2206L52.5978 8.45854C53.4103 7.1501 54.571 6.26832 56.8924 6.26832Z\"\n fill=\"#00EC97\"\n />\n <path\n d=\"M60.7221 23.961H59.2422L67.4542 0.124512H68.9341L60.7221 23.961ZM82.8081 8.08896V0.977843H86.8996V20.889H83.3304L82.9242 18.8694C81.9376 20.121 80.4867 21.0596 78.3394 21.0596C74.335 21.0596 71.4042 18.1867 71.4042 13.5503C71.4042 9.02762 74.335 6.09785 78.3104 6.09785C80.3706 6.09785 81.8505 6.89429 82.8081 8.08896ZM79.239 17.7885C81.4733 17.7885 82.8662 16.0818 82.8662 13.6072C82.8662 11.1041 81.4733 9.36896 79.239 9.36896C77.0046 9.36896 75.5827 11.0756 75.5827 13.5787C75.5827 16.0818 77.0046 17.7885 79.239 17.7885ZM96.2521 21.0596C91.2901 21.0596 88.3013 18.1298 88.3013 13.6072C88.3013 9.05607 91.3482 6.09785 95.962 6.09785C100.431 6.09785 103.449 8.82851 103.507 13.1236C103.507 13.5503 103.478 14.0338 103.39 14.489H92.5669V14.6881C92.6539 16.7645 94.0178 18.0161 96.078 18.0161C97.732 18.0161 98.8927 17.305 99.2409 15.9396H103.274C102.81 18.7841 100.228 21.0596 96.2521 21.0596ZM92.6539 11.8721H99.386C99.0959 10.0801 97.8481 9.08451 95.991 9.08451C94.2209 9.08451 92.8861 10.137 92.6539 11.8721ZM108.081 20.889L102.713 6.26851H107.094L110.692 16.793L114.233 6.26851H118.527L113.159 20.889H108.081ZM120.906 23.961H119.427L127.639 0.124512H129.118L120.906 23.961ZM140.671 6.09785C143.979 6.09785 146.707 7.83296 146.707 12.7823V20.889H142.615V13.1236C142.615 10.7343 141.629 9.3974 139.597 9.3974C137.508 9.3974 136.26 10.8765 136.26 13.3796V20.889H132.169V0.977843H136.26V8.06051C137.218 6.92273 138.553 6.09785 140.671 6.09785ZM158.367 13.5787V6.26851H162.459V20.889H158.832L158.454 19.1254C157.497 20.2632 156.191 21.0596 154.073 21.0596C150.997 21.0596 148.153 19.5521 148.153 14.3752V6.26851H152.245V13.8347C152.245 16.4516 153.115 17.7316 155.146 17.7316C157.178 17.7316 158.367 16.281 158.367 13.5787ZM173.022 6.09785C177.027 6.09785 179.928 8.91385 179.928 13.5503C179.928 18.073 177.027 21.0596 172.993 21.0596C170.846 21.0596 169.366 20.1494 168.408 18.8978L168.002 20.889H164.433V0.977843H168.524V8.1174C169.511 6.95118 170.962 6.09785 173.022 6.09785ZM172.094 17.7885C174.328 17.7885 175.779 16.0818 175.779 13.5787C175.779 11.0756 174.328 9.36896 172.094 9.36896C169.859 9.36896 168.466 11.0756 168.466 13.5503C168.466 16.0534 169.859 17.7885 172.094 17.7885Z\"\n fill=\"#151515\"\n />\n </g>\n <defs>\n <clipPath id=\"clip0_530_29\">\n <rect width=\"180\" height=\"24\" fill=\"white\" />\n </clipPath>\n </defs>\n </svg>\n </Link>\n </Wrapper>\n );\n};\n\nconst ProfileIcon = () => {\n const Wrapper = styled.svg`\n padding: 0.25rem;\n @media screen and (max-width: 768px) {\n display: none;\n }\n `;\n return (\n <Link\n to={linkHref({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"profile\", accountId: context.accountId },\n })}\n >\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.ProfileCard\"\n props={{ iconOnly: true, accountId: context.accountId || null }}\n />\n </Link>\n );\n};\n\nconst MenuIcon = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n style={{ height: 20, width: 20 }}\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M2 12.2986H14V13.3732H2V12.2986ZM2 9.07471H14V10.1493H2V9.07471ZM2 5.85083H14V6.92546H2V5.85083ZM2 2.62695H14V3.70158H2V2.62695Z\"\n fill=\"#818181\"\n />\n </svg>\n);\n\nconst Navbar = styled.div`\n padding: 1.5rem 0rem;\n\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n\n background: #f4f4f4;\n\n @media screen and (max-width: 768px) {\n padding: 1.875rem 1.375rem;\n }\n`;\n\nconst LinksContainer = styled.div`\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 1.5rem;\n\n @media screen and (max-width: 768px) {\n display: none;\n }\n`;\n\nconst MobileMenu = styled.button`\n all: unset;\n display: none;\n\n @media screen and (max-width: 768px) {\n display: block;\n }\n`;\n\nlet links = [\n {\n title: \"/feed\",\n href: \"announcements\",\n links: [],\n },\n {\n title: \"/communities\",\n href: \"communities\",\n links: [],\n },\n {\n title: \"/proposals\",\n href: \"proposals\",\n links: [],\n },\n {\n title: \"/about\",\n links: [\n { title: \"mission\", href: \"about\" },\n { title: \"blog\", href: \"blog\" },\n { title: \"newsletter\", href: \"https://newsletter.neardevhub.org\" },\n {\n title: \"calendar\",\n href: \"https://calendar.google.com/calendar/embed?height=600&wkst=2&bgcolor=%23ffffff&ctz=UTC&title&showNav=1&showDate=1&mode=AGENDA&showPrint=0&src=Y19mNTRlZDM3ZmQ5MjMyN2FjZGM3ZTQzNDNmZTQwNzIyYWU1Nzk3YjZjODI5MjliYTkzZTlmM2E4OWM2OTY1N2FiQGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20&color=%23616161\",\n },\n {\n title: \"brand kit\",\n href: \"https://drive.google.com/drive/folders/1C0GMmGq3MzbVPpxvf9807IU-7kpc2_v5?usp=sharing\",\n },\n ],\n },\n];\n\nif (hasModerator) {\n const isDevHubModerator = hasModerator({\n account_id: context.accountId,\n });\n\n if (isDevHubModerator) {\n links = [\n {\n title: \"/admin\",\n href: \"admin\",\n links: [],\n },\n ...links,\n ];\n }\n}\n\nconst MobileNav = styled.div`\n display: none;\n\n @media screen and (max-width: 768px) {\n display: flex;\n }\n\n position: absolute;\n top: 0;\n right: 0;\n\n width: 207px;\n\n padding: 24px 36px 36px 16px;\n flex-direction: column;\n align-items: flex-end;\n gap: 2.5rem;\n flex-shrink: 0;\n\n border-radius: 0px 0px 0px 16px;\n background: rgba(41, 41, 41, 0.6);\n backdrop-filter: blur(5px);\n\n z-index: 50;\n`;\n\nconst MobileLink = styled.a`\n color: #f4f4f4 !important;\n font-size: 20px;\n font-style: normal;\n font-weight: 400;\n line-height: 20px; /* 100% */\n margin-bottom: 1rem;\n\n &.active {\n color: #00ec97 !important;\n }\n\n &:hover {\n text-decoration: none;\n color: #00ec97 !important;\n }\n`;\n\nreturn (\n <Navbar className=\"position-relative\">\n <Logo />\n <div className=\"d-flex gap-3 align-items-center\">\n <LinksContainer>\n {links.map((link) => (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.NavbarDropdown\"\n props={{\n title: link.title,\n href: link.href,\n links: link.links,\n page: page,\n }}\n />\n ))}\n </LinksContainer>\n {context.accountId && <ProfileIcon />}\n <MobileMenu onClick={() => setShowMenu(!showMenu)}>\n <MenuIcon />\n </MobileMenu>\n </div>\n {showMenu && (\n <MobileNav>\n <div\n onClick={() => setShowMenu(!showMenu)}\n style={{ cursor: \"pointer\" }}\n >\n <i className=\"bi bi-x\" style={{ fontSize: 20, color: \"#F4F4F4\" }}></i>\n </div>\n <div className=\"d-flex flex-column gap-2\">\n {links.map((link, idx) =>\n link.href ? (\n <MobileLink\n key={`mobile-link-${idx}`}\n className={link.href === props.page && \"active\"}\n href={`/devhub.megha19.near/widget/app?page=${link.href}`}\n >\n {link.title}\n </MobileLink>\n ) : (\n link.links.map((it, idx) =>\n it.href.startsWith(\"http://\") ||\n it.href.startsWith(\"https://\") ? (\n <MobileLink\n key={`nested-link-${idx}`}\n className={link.href === props.page && \"active\"}\n href={it.href}\n target=\"no_blank\"\n >\n /{it.title}\n </MobileLink>\n ) : (\n <MobileLink\n key={`nested-link-${idx}`}\n className={link.href === props.page && \"active\"}\n href={`/devhub.megha19.near/widget/app?page=${it.href}`}\n >\n /{it.title}\n </MobileLink>\n )\n )\n )\n )}\n </div>\n </MobileNav>\n )}\n </Navbar>\n);\n" }, "devhub.entity.community.configuration.AboutConfigurator": { "": "const CommunityAboutSchema = {\n bio_markdown: {\n format: \"markdown\",\n\n inputProps: {\n min: 3,\n max: 200,\n\n placeholder:\n \"Tell people about your community. This will appear on your community’s homepage.\",\n required: true,\n resize: \"none\",\n },\n\n label: \"Bio\",\n multiline: true,\n order: 1,\n },\n\n twitter_handle: {\n inputProps: { prefix: \"https://twitter.com/\", min: 2, max: 60 },\n label: \"Twitter\",\n order: 2,\n },\n\n github_handle: {\n inputProps: { prefix: \"https://github.com/\", min: 2, max: 60 },\n label: \"Github\",\n order: 3,\n },\n\n telegram_handle: {\n inputProps: { prefix: \"https://t.me/\", min: 2, max: 60 },\n format: \"comma-separated\",\n label: \"Telegram\",\n order: 4,\n },\n\n website_url: {\n inputProps: { prefix: \"https://\", min: 2, max: 60, validUrl: true },\n label: \"Website\",\n order: 5,\n },\n};\n\nconst { data, onSubmit, onCancel, setIsActive, isActive } = props;\n\nfunction handleOnSubmit(v) {\n onSubmit(v);\n setIsActive(false);\n}\n\nreturn (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.organism.Configurator\"}\n props={{\n externalState: data,\n schema: CommunityAboutSchema,\n onSubmit: handleOnSubmit,\n isActive,\n onCancel: onCancel,\n }}\n />\n);\n" }, "devhub.feature.post-search.by-tag": { "": "const { getAllLabels } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nif (!getAllLabels) {\n return <p>Loading modules...</p>;\n}\n\nconst selectedTags = props.tag ? [{ name: props.tag }] : [];\n\nconst tags = getAllLabels();\n\nif (tags === null) {\n return <div>Loading ...</div>;\n}\n\nconst onChange = (selectedTags) =>\n props.onTagSearch?.(selectedTags[0]?.name ?? \"\");\n\nreturn (\n <Typeahead\n clearButton\n id=\"basic-typeahead-single\"\n labelKey=\"name\"\n options={tags.map((tag) => ({ name: tag }))}\n placeholder=\"Search by tag\"\n selected={selectedTags}\n {...{ onChange }}\n />\n);\n" }, "devhub.entity.addon.github.Configurator": { "": "const Struct = VM.require(\"devhub.megha19.near/widget/core.lib.struct\");\n\nif (!Struct) {\n return <p>Loading modules...</p>;\n}\nconst { useQuery } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\nconst { uuid, withUUIDIndex } = VM.require(\n \"devhub.megha19.near/widget/core.lib.uuid\"\n);\n\nuuid || (uuid = () => {});\nwithUUIDIndex || (withUUIDIndex = () => {});\nuseQuery || (useQuery = () => {});\n\nconst AttractableDiv = styled.div`\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\nconst settings = {\n maxColumnsNumber: 20,\n};\n\nconst GithubKanbanBoardTicketFeaturesSchema = {\n id: { label: \"GitHub ID\" },\n author: { label: \"Author\" },\n labels: { label: \"Labels\" },\n type: { label: \"Type\" },\n};\n\nconst GithubKanbanBoardTicketTypesSchema = {\n issue: { label: \"Issue\" },\n pullRequest: { label: \"Pull Request\" },\n};\n\nconst GithubKanbanBoardLabelsSchema = {\n allLabelsMust: { label: \"All labels must be present in ticket\" },\n};\n\nconst GithubKanbanBoardDefaults = {\n columns: {\n ...withUUIDIndex({\n description: \"\",\n labelSearchTerms: [],\n title: \"\",\n allLabelsMust: false,\n }),\n },\n dataTypesIncluded: { issue: true, pullRequest: true },\n description: \"\",\n repoURL: \"\",\n ticketState: \"all\",\n title: \"\",\n metadata: {\n id: uuid(),\n type: \"github.kanban_board\",\n ticket: {\n type: \"github.kanban_ticket\",\n features: { id: true, author: true, labels: true, type: true },\n },\n },\n};\n\nconst toMigrated = ({ metadata, id, ...restParams }) => ({\n ...GithubKanbanBoardDefaults,\n metadata: {\n ...GithubKanbanBoardDefaults.metadata,\n ...metadata,\n id: id ?? metadata.id,\n },\n ...restParams,\n});\n\nfunction extractOwnerAndRepo(url) {\n // Remove any leading or trailing slashes and split the URL by \"/\"\n const parts = url\n .trim()\n .replace(/^\\/+|\\/+$/g, \"\")\n .split(\"/\");\n\n // Check if the URL matches the GitHub repository format\n if (parts.length === 5 && parts[2] === \"github.com\") {\n const owner = parts[3];\n const repo = parts[4];\n return { owner, repo };\n } else {\n return null;\n }\n}\n\nfunction isValidGitHubRepoLink(url) {\n // Regular expression to match GitHub repository URLs\n const githubRepoRegex =\n /^(?:https?:\\/\\/)?(?:www\\.)?github\\.com\\/([^\\/]+)\\/([^\\/]+)\\/?$/;\n\n // Check if the URL matches the GitHub repository format\n return githubRepoRegex.test(url);\n}\n\nconst GithubViewConfigurator = ({ kanbanBoards, permissions, onSubmit }) => {\n const data = kanbanBoards ? Object.values(kanbanBoards)?.[0] : {};\n\n if (!data) {\n return (\n <div class=\"alert alert-danger\" role=\"alert\">\n Loading...\n </div>\n );\n }\n\n const initialBoardState = Struct.typeMatch(data)\n ? toMigrated(data)\n : GithubKanbanBoardDefaults;\n\n const getColumnData = useCallback((state) => {\n if (Object.keys(state).length > 0) {\n return state?.columns ?? {};\n }\n return state;\n }, []);\n\n const getNonColumnData = useCallback((state) => {\n if (Object.keys(state).length > 0) {\n delete state.columns;\n return state;\n }\n return state;\n }, []);\n\n // to improve the state update speed, decoupled columns and other configuration metadata\n const [parentState, setParentState] = useState(initialBoardState);\n const [metadataState, setMetadata] = useState(\n getNonColumnData(initialBoardState)\n );\n const [showPreview, setPreview] = useState(false);\n const [columnsState, setColumnsState] = useState(\n getColumnData(initialBoardState)\n );\n const [repoLabels, setRepoLabels] = useState([]);\n\n function fetchLabelsFromRepo(url) {\n const data = extractOwnerAndRepo(url);\n if (data) {\n const { repo, owner } = data;\n useCache(\n () =>\n asyncFetch(\n `https://api.github.com/repos/${owner}/${repo}/labels`\n ).then((res) => {\n if (Array.isArray(res.body)) {\n const labels = [];\n res.body.map((item) => {\n labels.push(item.name);\n });\n setRepoLabels(labels);\n }\n }),\n owner + repo + \"labels\",\n { subscribe: false }\n );\n }\n }\n\n useEffect(() => {\n if (metadataState.repoURL && isValidGitHubRepoLink(metadataState.repoURL)) {\n fetchLabelsFromRepo(metadataState.repoURL);\n }\n }, [metadataState]);\n\n const formUpdate =\n ({ path, via: customFieldUpdate, isColumnsUpdate, ...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 (isColumnsUpdate ? { columns: columnsState } : metadataState) ?? {},\n path,\n (node) => transformFn(node)\n );\n if (isColumnsUpdate) {\n setColumnsState(updatedValues?.columns);\n } else {\n setMetadata((prevFormState) => ({\n ...prevFormState,\n ...updatedValues,\n }));\n }\n };\n\n const formReset = () => {\n setColumnsState(getColumnData(initialBoardState));\n setMetadata(getNonColumnData(initialBoardState));\n setParentState(initialBoardState);\n };\n\n const columnsCreateNew = ({ lastKnownValue }) =>\n Object.keys(lastKnownValue).length < settings.maxColumnsNumber\n ? {\n ...(lastKnownValue ?? {}),\n ...withUUIDIndex({\n description: \"\",\n labelSearchTerms: [],\n title: \"\",\n allLabelsMust: false,\n }),\n }\n : lastKnownValue;\n\n const columnsDeleteById =\n (id) =>\n ({ lastKnownValue }) =>\n Object.fromEntries(\n Object.entries(lastKnownValue).filter(([columnId]) => columnId !== id)\n );\n\n const onCancel = () => {\n formReset();\n };\n\n const updateParentState = () => {\n const updatedState = { ...metadataState, columns: columnsState };\n setParentState(updatedState);\n return updatedState;\n };\n\n const onSave = () => onSubmit(updateParentState());\n\n const formElement = (\n <>\n <div className=\"d-flex flex-column\">\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"w-100\",\n key: `${metadataState.metadata.id}-repoURL`,\n label: \"Repository URL\",\n onChange: formUpdate({\n path: [\"repoURL\"],\n isColumnsUpdate: false,\n }),\n placeholder: \"https://github.com/example-org/example-repo\",\n value: metadataState.repoURL ?? \"\",\n }}\n />\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"w-100\",\n key: `${metadataState.metadata.id}-title`,\n label: \"Title\",\n onChange: formUpdate({ path: [\"title\"], isColumnsUpdate: false }),\n placeholder: \"NEAR Protocol NEPs\",\n value: metadataState.title ?? \"\",\n }}\n />\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"w-100\",\n key: `${metadataState.metadata.id}-description`,\n label: \"Description\",\n onChange: formUpdate({\n path: [\"description\"],\n isColumnsUpdate: false,\n }),\n placeholder: \"Latest NEAR Enhancement Proposals by status.\",\n value: metadataState.description ?? \"\",\n }}\n />\n </div>\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2\">\n <label>Select which tasks you want to display:</label>\n <div className=\"input-group\" style={{ width: \"fit-content\" }}>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.organism.Configurator`}\n props={{\n heading: \"Ticket types\",\n classNames: { root: \"col-12 col-md-4 h-auto\" },\n externalState: metadataState.dataTypesIncluded,\n isActive: true,\n isEmbedded: true,\n isUnlocked: permissions.can_configure,\n onChange: formUpdate({\n path: [\"dataTypesIncluded\"],\n isColumnsUpdate: false,\n }),\n schema: GithubKanbanBoardTicketTypesSchema,\n hideSubmitBtn: true,\n }}\n />\n </div>\n </div>\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2\">\n <label>Select which state of tickets you want to display:</label>\n <div className=\"input-group mt-2\">\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Switch`}\n props={{\n currentValue: metadataState.ticketState,\n key: \"ticketState\",\n onChange: formUpdate({\n path: [\"ticketState\"],\n isColumnsUpdate: false,\n }),\n options: [\n { label: \"All\", value: \"all\" },\n { label: \"Open\", value: \"open\" },\n { label: \"Closed\", value: \"closed\" },\n ],\n }}\n />\n </div>\n </div>\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2\">\n <label>\n Select which items you want to display on each card in a column:\n </label>\n <div className=\"input-group\" style={{ width: \"fit-content\" }}>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.organism.Configurator`}\n props={{\n heading: \"Card fields\",\n classNames: { root: \"col-12 col-md-4 h-auto\" },\n externalState: metadataState.metadata.ticket.features,\n isActive: true,\n isEmbedded: true,\n isUnlocked: permissions.can_configure,\n onChange: formUpdate({\n path: [\"metadata\", \"ticket\", \"features\"],\n isColumnsUpdate: false,\n }),\n schema: GithubKanbanBoardTicketFeaturesSchema,\n hideSubmitBtn: true,\n }}\n />\n </div>\n </div>\n\n <div className=\"d-flex align-items-center justify-content-between mb-2\">\n <span className=\"d-inline-flex gap-2 m-0\">\n <i className=\"bi bi-list-task\" />\n <span>{`Each board configuration ( maximum allowed - ${settings.maxColumnsNumber} ) : `}</span>\n </span>\n </div>\n\n <div className=\"d-flex flex-column align-items-center gap-3 w-100 boardconfiguration\">\n {Object.values(columnsState ?? {}).map(\n (\n { id, description, labelSearchTerms, title, allLabelsMust },\n index\n ) => (\n <AttractableDiv\n className=\"d-flex gap-3 rounded-4 border p-3 w-100\"\n key={`column-${id}-configurator`}\n >\n <div className=\"d-flex flex-column gap-1 w-100\">\n <div>Board #{index}</div>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n key: `${metadataState.metadata.id}-column-${id}-title`,\n label: \"Title\",\n onChange: formUpdate({\n path: [\"columns\", id, \"title\"],\n isColumnsUpdate: true,\n }),\n placeholder: \"👀 Review\",\n value: title,\n }}\n />\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2\">\n <label>Search tickets using labels:</label>\n <div className=\"input-group\">\n <Typeahead\n id=\"hashtags\"\n onChange={(data) => {\n const formUpdateFunc = formUpdate({\n path: [\"columns\", id, \"labelSearchTerms\"],\n isColumnsUpdate: true,\n });\n return formUpdateFunc(data.join(\", \"));\n }}\n selected={labelSearchTerms?.[0] ? labelSearchTerms : []}\n multiple\n labelKey=\"hashtags\"\n emptyLabel=\"Find your unique label\"\n placeholder=\"WG-, draft, review, proposal,\"\n options={repoLabels}\n />\n </div>\n </div>\n <div style={{ width: \"fit-content\" }}>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.organism.Configurator`}\n props={{\n heading: \"\",\n classNames: { root: \"col-12 col-md-4 h-auto\" },\n externalState: { allLabelsMust: allLabelsMust },\n isActive: true,\n isEmbedded: true,\n isUnlocked: permissions.can_configure,\n onChange: (data) => {\n const formUpdateFunc = formUpdate({\n path: [\"columns\", id, \"allLabelsMust\"],\n isColumnsUpdate: true,\n });\n return formUpdateFunc(data[\"allLabelsMust\"]);\n },\n schema: GithubKanbanBoardLabelsSchema,\n hideSubmitBtn: true,\n }}\n />\n </div>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n key: `${metadataState.metadata.id}-column-${id}-description`,\n label: \"Description\",\n onChange: formUpdate({\n path: [\"columns\", id, \"description\"],\n isColumnsUpdate: true,\n }),\n placeholder:\n \"NEPs that need a review by Subject Matter Experts.\",\n value: description,\n }}\n />\n </div>\n\n <div\n className=\"d-flex flex-column gap-3 border-start p-3 pe-0\"\n style={{ marginTop: -16, marginBottom: -16 }}\n >\n <button\n className=\"btn btn-outline-danger\"\n onClick={formUpdate({\n path: [\"columns\"],\n via: columnsDeleteById(id),\n isColumnsUpdate: true,\n })}\n title=\"Delete column\"\n >\n <i className=\"bi bi-trash-fill\" />\n </button>\n </div>\n </AttractableDiv>\n )\n )}\n </div>\n </>\n );\n\n return (\n <div\n className=\"d-flex flex-column gap-4 w-100\"\n style={{ maxWidth: \"100%\" }}\n >\n <ul className=\"nav nav-tabs\" id=\"editPreviewTabs\" role=\"tablist\">\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className={`nav-link ${!showPreview ? \"active\" : \"\"}`}\n id=\"edit-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#edit\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"edit\"\n aria-selected=\"true\"\n onClick={() => setPreview(false)}\n >\n Edit\n </button>\n </li>\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className={`nav-link ${showPreview ? \"active\" : \"\"}`}\n id=\"preview-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#preview\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"preview\"\n aria-selected=\"false\"\n onClick={() => {\n updateParentState();\n setPreview(true);\n }}\n >\n Preview\n </button>\n </li>\n </ul>\n {showPreview ? (\n <div>\n <Widget\n src={`devhub.megha19.near/widget/devhub.entity.addon.github.Viewer`}\n props={{\n kanbanBoards: {\n [parentState.metadata.id]: parentState,\n },\n }}\n />\n </div>\n ) : (\n <div className={\"d-flex flex-column gap-4 w-100\"}>\n <div className={\"d-flex flex-column gap-2 w-100\"}>\n <div className=\"d-flex align-items-center justify-content-between gap-3 w-100\">\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n <i className=\"bi bi-gear-wide-connected\" />\n <span>GitHub board configuration</span>\n </h5>\n </div>\n <div>\n This configuration enables integration of your GitHub repository\n as a Kanban board, facilitating issue and pull request tracking.\n You can create distinct columns to organize various items, each\n with unique labels.\n </div>\n </div>\n {Object.keys(parentState).length > 0 && (\n <div>\n {formElement}\n <div className=\"d-flex justify-content-between gap-2 mt-4\">\n <div style={{ minWidth: \"200px\" }}>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"btn-sm btn-outline-secondary\",\n },\n label: \"Add another column\",\n disabled:\n parentState.columns &&\n Object.keys(parentState.columns).length >=\n settings.maxColumnsNumber,\n icon: { type: \"bootstrap_icon\", variant: \"bi-plus-lg\" },\n onClick: formUpdate({\n path: [\"columns\"],\n via: columnsCreateNew,\n isColumnsUpdate: true,\n }),\n }}\n />\n </div>\n <div className=\"d-flex gap-3 justify-content-end w-100\">\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"d-flex btn btn-outline-danger shadow-none border-0\",\n },\n isHidden: typeof onCancel !== \"function\",\n label: \"Cancel\",\n onClick: onCancel,\n }}\n />\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: { root: \"btn btn-success\" },\n icon: {\n type: \"svg_icon\",\n variant: \"floppy_drive\",\n width: 14,\n height: 14,\n },\n isHidden: typeof onSave !== \"function\",\n label: \"Save\",\n onClick: onSave,\n }}\n />\n </div>\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n );\n};\n\nreturn GithubViewConfigurator(props);\n" }, "devhub.feature.proposal-search.by-sort": { "": "const options = [\n { label: \"Most recent\", value: \"proposal_id\" }, // proposal_id desc\n { label: \"Most viewed\", value: \"views\" }, // views desc\n // TODO add track_comments function to devhub to track it in the indexer\n // { label: \"Most commented\", value: \"\" }, // comments desc\n // { label: \"Unanswered\", value: \"\" }, // where comments = 0\n { label: \"None\", value: \"\" }, // where comments = 0\n];\n\nconst setSelected = props.onStateChange ?? (() => {});\n\nreturn (\n <div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: options,\n label: \"Sort\",\n selectedValue: options[0],\n onUpdate: (v) => {\n setSelected(v);\n },\n }}\n />\n </div>\n);\n" }, "devhub.entity.addon.blog.editor.layout": { "": "const { Sidebar, Content, getData, editData } = props;\n\nconst [selectedItem, setSelectedItem] = useState(editData);\n\nconst handleItemClick = (item) => {\n if (item) {\n getData(item).then((item) => {\n setSelectedItem(item);\n });\n } else {\n setSelectedItem(null);\n }\n};\n\nreturn (\n <div\n className=\"template\"\n style={{ display: \"flex\", width: \"100%\", height: \"100%\" }}\n >\n <div\n className=\"left-panel\"\n style={{\n margin: \"20px 20px 80px 20px\",\n }}\n >\n <Sidebar selectedItem={selectedItem} handleItemClick={handleItemClick} />\n </div>\n <div\n className=\"right-panel\"\n style={{ flex: 1, width: 0, overflow: \"scroll\" }}\n key={selectedItem.id}\n >\n <Content data={selectedItem} />\n </div>\n </div>\n);\n" }, "devhub.entity.post.draft": { "": "const DRAFT_STATE_STORAGE_KEY = \"POST_DRAFT_STATE\";\n\nconst onDraftStateChange = (draftState) =>\n Storage.privateSet(DRAFT_STATE_STORAGE_KEY, JSON.stringify(draftState));\nlet draftState;\ntry {\n draftState = JSON.parse(Storage.privateGet(DRAFT_STATE_STORAGE_KEY));\n} catch (e) {}\n\nreturn { DRAFT_STATE_STORAGE_KEY, draftState, onDraftStateChange };\n" }, "devhub.components.atom.Tag": { "": "const black = props.black;\n\nconst Span = styled.span`\n color: ${black ? \"#818181\" : \"#00ec97\"};\n font-size: 16px;\n font-style: normal;\n font-weight: 700;\n line-height: 20px; /* 125% */\n`;\n\nconst Tag = ({ tag }) => <Span>{tag}</Span>;\n\nreturn Tag(props);\n" }, "devhub.entity.addon.blog.editor.sidebar": { "": "const { data, editPostId, handleItemClick, selectedItem } = props;\n\nconst SidebarContainer = styled.div`\n background-color: #f0f0f0;\n padding: 16px;\n width: 200px;\n display: flex;\n flex-direction: column;\n align-items: center;\n height: 100%;\n gap: 4px;\n`;\n\nconst SidebarButton = styled.button`\n display: flex;\n padding: 14px 16px;\n text-align: center;\n cursor: pointer;\n gap: 16px;\n width: 100%;\n\n border-radius: 4px;\n border: 1px solid #00ec97;\n\n background-color: ${({ selected }) => (selected ? \"#00ec97\" : \"white\")};\n color: ${({ selected }) => (selected ? \"white\" : \"black\")};\n`;\n\nreturn (\n <SidebarContainer id=\"edit-blog-sidebar\">\n <p>Blog posts</p>\n <SidebarButton\n selected={!selectedItem.id}\n onClick={() => handleItemClick(null)}\n id=\"create-new-blog\"\n >\n New\n </SidebarButton>\n {(data || []).map((it) => (\n <SidebarButton\n id={`edit-blog-selector-${it.post_id}`}\n key={it.post_id}\n selected={parseInt(selectedItem.id) === it.post_id}\n onClick={() => handleItemClick(it.post_id)}\n >\n Id: {it.post_id}\n </SidebarButton>\n ))}\n </SidebarContainer>\n);\n" }, "devhub.feature.proposal-search.by-author": { "": "const [authorsOptions, setAuthorsOptions] = useState([]);\nconst [selectedAuthor, setSelectedAuthor] = useState(null);\n\nif (!authorsOptions.length) {\n const data = [{ label: \"None\", value: \"\" }];\n const authors = Near.view(\n \"devhub.near\",\n \"get_all_proposal_authors\",\n {}\n );\n\n if (Array.isArray(authors)) {\n for (const author of authors) {\n data.push({ label: author, value: author });\n }\n setAuthorsOptions(data);\n }\n}\n\nconst Container = styled.div`\n .dropdown-menu {\n max-height: 400px;\n overflow-x: auto;\n }\n`;\nreturn (\n <Container>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: authorsOptions,\n label: \"Author\",\n onUpdate: (v) => {\n setSelectedAuthor(v);\n props.onAuthorChange(v);\n },\n selectedValue: props.author,\n }}\n />\n </Container>\n);\n" }, "devhub.page.announcements": { "": "const MainContent = styled.div`\n padding-left: 2rem;\n padding-right: 2rem;\n flex: 3;\n @media screen and (max-width: 960px) {\n padding-left: 0rem;\n padding-right: 0rem;\n }\n .post:hover {\n background-color: inherit !important;\n }\n`;\n\nconst Heading = styled.div`\n font-size: 28px;\n font-weight: 600;\n margin-bottom: 1rem;\n`;\n\nconst SubHeading = styled.div`\n font-size: 15px;\n font-weight: 600;\n`;\n\nconst Container = styled.div`\n flex-wrap: no-wrap;\n max-width: 100%;\n\n background: #fff;\n\n .max-width-100 {\n max-width: 100%;\n }\n @media screen and (max-width: 960px) {\n flex-wrap: wrap;\n }\n\n .card {\n border-radius: 1rem !important;\n }\n\n .display-none {\n display: none;\n }\n`;\n\nconst TabContainer = styled.div`\n display: flex;\n gap: 5px;\n background-color: rgb(244, 244, 244);\n justify-content: center;\n align-items: center;\n border-radius: 5px;\n padding: 0 3px;\n`;\n\nconst Tab = styled.div`\n cursor: pointer;\n font-size: 14px;\n font-weight: 700;\n color: rgb(153, 153, 153);\n padding: 5px 10px;\n border-radius: 5px;\n`;\n\nconst Line = styled.div`\n width: 100%;\n border-bottom: 1px solid rgb(223, 223, 223);\n`;\n\nconst tabs = [\"All\", \"Announcements\", \"Discussions\"];\nconst [selectedTab, setSelectedTab] = useState(\"All\");\nconst [sort, setSort] = useState(\"desc\");\n\nconst followGraph = Social.keys(\n `community.devhub.near/graph/follow/*`,\n \"final\"\n);\nconst accountsFollowing =\n props.accountsFollowing ??\n (followGraph\n ? Object.keys(followGraph[\"community.devhub.near\"].graph.follow || {})\n : null);\n\nconst filteredAccountIds =\n accountsFollowing &&\n accountsFollowing.filter((account) => {\n if (selectedTab === \"All\") return true;\n if (selectedTab === \"Announcements\") {\n return !account.includes(\"discussions\");\n }\n if (selectedTab === \"Discussions\") {\n return account.includes(\"discussions\");\n }\n });\n\nreturn (\n <div className=\"w-100\" style={{ maxWidth: \"100%\" }}>\n <Heading>Activity Feed</Heading>\n <Line />\n <Container className=\"d-flex gap-3 px-2 py-4\">\n <MainContent className=\"max-width-100\">\n <div className=\"d-flex flex-column gap-4\">\n <div className=\"d-flex flex-wrap justify-content-between\">\n <TabContainer>\n {tabs.map((tab) => (\n <Tab\n className={selectedTab === tab ? \"text-black bg-white\" : \"\"}\n onClick={() => setSelectedTab(tab)}\n >\n {tab}\n </Tab>\n ))}\n </TabContainer>\n <div className={\"d-flex align-items-center gap-2\"}>\n <select\n name=\"sort\"\n id=\"sort\"\n class=\"form-select border-0\"\n value={sort}\n onChange={(e) => {\n setSort(e.target.value);\n }}\n >\n <option selected value=\"desc\">\n Latest\n </option>\n <option value=\"recentcommentdesc\">Last Commented</option>\n </select>\n </div>\n </div>\n\n <div\n className={\"card p-4\"}\n style={{ overflow: \"auto\", height: \"60vh\" }}\n >\n <Widget\n key=\"feed\"\n src=\"devhub.megha19.near/widget/devhub.components.feed.SubscribedFeed\"\n props={{\n sort: sort,\n accounts: filteredAccountIds,\n threshold: 250,\n }}\n />\n </div>\n </div>\n </MainContent>\n </Container>\n </div>\n);\n" }, "devhub.page.profile": { "": "const accountId = props.accountId || context.accountId;\n\nif (!accountId) {\n return \"No Account Logged In!\";\n}\n\nreturn (\n <div className=\"w-100 bg-white overflow-hidden px-3\">\n <Widget src=\"mob.near/widget/ProfilePage\" props={{ accountId }} />\n </div>\n);\n" }, "devhub.components.molecule.MarkdownEditor": { "": "const MarkdownEditor = ({ data, onChange, showAutoComplete }) => {\n return (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.SimpleMDE\"}\n props={{\n data,\n onChange,\n showAutoComplete,\n }}\n />\n );\n};\n\nreturn MarkdownEditor(props);\n" }, "devhub.entity.addon.github.Viewer": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\nconst { useQuery } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nuseQuery || (useQuery = () => {});\n\nhref || (href = () => {});\nconst { kanbanBoards, handle, permissions } = props;\n\nconst data = Object.values(kanbanBoards ?? {})?.[0];\n\nif (!kanbanBoards || !data?.metadata) {\n return (\n <div\n className=\"d-flex flex-column align-items-center justify-content-center gap-4\"\n style={{ height: 384 }}\n >\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n {permissions.can_configure\n ? \"You can configure the board by clicking on the settings icon.\"\n : \"This board isn't configured yet.\"}\n </h5>\n </div>\n );\n}\n\nreturn (\n <Widget\n src={`devhub.megha19.near/widget/devhub.entity.addon.${data.metadata.type}`}\n props={{\n ...data,\n isConfiguratorActive: false,\n isSynced: true,\n permissions,\n }}\n />\n);\n" }, "devhub.components.feed.MergedIndexFeed": { "": "if (!props.index) {\n return \"props.index is not defined\";\n}\nconst indices = JSON.parse(\n JSON.stringify(Array.isArray(props.index) ? props.index : [props.index])\n);\n\nconst filter = props.filter;\n\nconst renderItem =\n props.renderItem ??\n ((item) => (\n <div key={JSON.stringify(item)} style={{ minHeight: \"150px\" }}>\n #{item.blockHeight}: {JSON.stringify(item)}\n </div>\n ));\nconst cachedRenderItem = (item, i) => {\n const key = JSON.stringify(item);\n\n if (!(key in state.cachedItems)) {\n state.cachedItems[key] = renderItem(item, i);\n State.update();\n }\n return state.cachedItems[key];\n};\n\nconst initialRenderLimit = props.initialRenderLimit ?? 10;\nconst addDisplayCount = props.nextLimit ?? initialRenderLimit;\nconst reverse = !!props.reverse;\n\nconst computeFetchFrom = (items, limit, desc) => {\n if (!items || items.length < limit) {\n return false;\n }\n const blockHeight = items[items.length - 1].blockHeight;\n return desc ? blockHeight - 1 : blockHeight + 1;\n};\n\nconst mergeItems = (iIndex, oldItems, newItems, desc) => {\n const index = indices[iIndex];\n const items = [\n ...new Set(\n [\n ...newItems.map((item) => ({\n ...item,\n action: index.action,\n key: index.key,\n index: iIndex,\n })),\n ...oldItems,\n ].map((i) => JSON.stringify(i))\n ),\n ].map((i) => JSON.parse(i));\n items.sort((a, b) => a.blockHeight - b.blockHeight);\n if (desc) {\n items.reverse();\n }\n return items;\n};\n\nconst jIndices = JSON.stringify(indices);\nif (jIndices !== state.jIndices) {\n State.update({\n jIndices,\n feeds: indices.map(() => ({})),\n items: [],\n displayCount: initialRenderLimit,\n cachedItems: {},\n });\n}\n\nlet stateChanged = false;\nfor (let iIndex = 0; iIndex < indices.length; ++iIndex) {\n const index = indices[iIndex];\n const feed = state.feeds[iIndex];\n let feedChanged = false;\n index.options = index.options || {};\n index.options.limit = Math.min(\n Math.max(initialRenderLimit + addDisplayCount * 2, index.options.limit),\n 100\n );\n const desc = index.options.order === \"desc\";\n\n const initialItems = Social.index(\n index.action,\n index.key,\n index.options,\n index.cacheOptions\n );\n if (initialItems === null) {\n continue;\n }\n\n const jInitialItems = JSON.stringify(initialItems);\n const nextFetchFrom = computeFetchFrom(\n initialItems,\n index.options.limit,\n desc\n );\n if (feed.jInitialItems !== jInitialItems) {\n feed.jInitialItems = jInitialItems;\n feedChanged = true;\n if (nextFetchFrom !== feed.initialNextFetchFrom) {\n feed.fetchFrom = false;\n feed.items = mergeItems(iIndex, [], initialItems, desc);\n feed.initialNextFetchFrom = nextFetchFrom;\n feed.nextFetchFrom = nextFetchFrom;\n } else {\n feed.items = mergeItems(iIndex, feed.items, initialItems, desc);\n }\n }\n\n feed.usedCount = 0;\n\n if (feedChanged) {\n state.feeds[iIndex] = feed;\n stateChanged = true;\n }\n}\n\n// Construct merged feed and compute usage per feed.\n\nconst filteredItems = [];\nwhile (filteredItems.length < state.displayCount) {\n let bestItem = null;\n for (let iIndex = 0; iIndex < indices.length; ++iIndex) {\n const index = indices[iIndex];\n const feed = state.feeds[iIndex];\n const desc = index.options.order === \"desc\";\n if (!feed.items) {\n continue;\n }\n const item = feed.items[feed.usedCount];\n if (!item) {\n continue;\n }\n if (\n bestItem === null ||\n (desc\n ? item.blockHeight > bestItem.blockHeight\n : item.blockHeight < bestItem.blockHeight)\n ) {\n bestItem = item;\n }\n }\n if (!bestItem) {\n break;\n }\n state.feeds[bestItem.index].usedCount++;\n if (filter) {\n if (filter.ignore) {\n if (bestItem.accountId in filter.ignore) {\n continue;\n }\n }\n if (filter.require) {\n if (!(bestItem.accountId in filter.require)) {\n continue;\n }\n }\n }\n filteredItems.push(bestItem);\n}\n\n// Fetch new items for feeds that don't have enough items.\nfor (let iIndex = 0; iIndex < indices.length; ++iIndex) {\n const index = indices[iIndex];\n const feed = state.feeds[iIndex];\n const desc = index.options.order === \"desc\";\n let feedChanged = false;\n\n if (\n (feed.items.length || 0) - feed.usedCount < addDisplayCount * 2 &&\n !feed.fetchFrom &&\n feed.nextFetchFrom &&\n feed.nextFetchFrom !== feed.fetchFrom\n ) {\n feed.fetchFrom = feed.nextFetchFrom;\n feedChanged = true;\n }\n\n if (feed.fetchFrom) {\n const limit = addDisplayCount;\n const newItems = Social.index(\n index.action,\n index.key,\n Object.assign({}, index.options, {\n from: feed.fetchFrom,\n subscribe: undefined,\n limit,\n })\n );\n if (newItems !== null) {\n feed.items = mergeItems(iIndex, feed.items, newItems, desc);\n feed.fetchFrom = false;\n feed.nextFetchFrom = computeFetchFrom(newItems, limit, desc);\n feedChanged = true;\n }\n }\n\n if (feedChanged) {\n state.feeds[iIndex] = feed;\n stateChanged = true;\n }\n}\n\nif (stateChanged) {\n State.update();\n}\n\nconst makeMoreItems = () => {\n State.update({\n displayCount: state.displayCount + addDisplayCount,\n });\n};\n\nconst loader = (\n <div className=\"loader\" key={\"loader\"}>\n <span\n className=\"spinner-grow spinner-grow-sm me-1\"\n role=\"status\"\n aria-hidden=\"true\"\n />\n Loading ...\n </div>\n);\n\nconst fetchMore =\n props.manual &&\n (state.feeds.some((f) => !!f.fetchFrom) &&\n filteredItems.length < state.displayCount\n ? loader\n : state.displayCount < filteredItems.length && (\n <div key={\"loader more\"}>\n <a href=\"javascript:void\" onClick={(e) => makeMoreItems()}>\n {props.loadMoreText ?? \"Load more...\"}\n </a>\n </div>\n ));\n\nconst items = filteredItems ? filteredItems.slice(0, state.displayCount) : [];\nif (reverse) {\n items.reverse();\n}\n\nconst renderedItems = items.map(cachedRenderItem);\n\nreturn props.manual ? (\n <>\n {reverse && fetchMore}\n {renderedItems}\n {!reverse && fetchMore}\n </>\n) : (\n <InfiniteScroll\n pageStart={0}\n loadMore={makeMoreItems}\n threshold={props.threshold ?? 250}\n hasMore={state.displayCount <= filteredItems.length}\n loader={loader}\n useWindow={false}\n >\n {renderedItems}\n </InfiniteScroll>\n);\n" }, "devhub.entity.addon.github.kanban_ticket": { "": "const AttractableDiv = styled.div`\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\nconst ticketStates = {\n closed: { displayName: \"Closed\", icon: \"bi-lock-fill\" },\n open: { displayName: \"Open\", icon: \"bi-unlock-fill\" },\n};\n\nconst ticketTypes = {\n Issue: { displayName: \"Issue\", icon: \"bi-lightbulb-fill\" },\n PullRequest: { displayName: \"Pull request\", icon: \"bi-git\" },\n};\n\nconst GithubKanbanTicket = ({\n metadata: { features },\n payload: {\n _links,\n labels,\n number,\n state: ticketState,\n title,\n type,\n user,\n url,\n },\n}) => {\n const header = (\n <div className=\"card-header\">\n <div class=\"d-flex justify-content-start gap-3\">\n <i\n className={`bi ${ticketStates[ticketState].icon}`}\n title={ticketStates[ticketState].displayName}\n />\n\n {features.author ? (\n <a\n className=\"d-flex gap-2 link-dark text-truncate\"\n href={user.html_url}\n rel=\"noreferrer\"\n target=\"_blank\"\n >\n <img\n alt={`${user.login}'s GitHub avatar`}\n className=\"img-fluid rounded\"\n src={user.avatar_url}\n style={{ width: 24, height: 24 }}\n />\n\n <span className=\"text-muted\">@{user.login}</span>\n </a>\n ) : null}\n\n <a\n className=\"card-link ms-auto\"\n href={_links?.html?.href ?? url}\n rel=\"noreferrer\"\n role=\"button\"\n target=\"_blank\"\n title=\"Open in new tab\"\n >\n <i className=\"bi bi-share\" />\n </a>\n </div>\n </div>\n );\n\n const titleArea = (\n <span className=\"card-text gap-2\">\n {features.type ? <i className={`bi ${ticketTypes[type].icon}`} /> : null}\n\n <span>\n {[\n `${features.type ? ticketTypes[type].displayName : \"\"} ${\n features.id ? `#${number.toString()}` : \"\"\n }`.trim(),\n\n title,\n ]\n .filter(\n (maybeString) =>\n typeof maybeString === \"string\" && maybeString.length > 0\n )\n .join(\": \")}\n </span>\n </span>\n );\n\n const labelList = features.labels ? (\n <div className=\"d-flex flex-wrap gap-2 m-0\">\n {(labels ?? []).map((label) => (\n <a href={label.url} key={label.id} title={label.description}>\n <span\n className=\"badge text-wrap\"\n style={{ backgroundColor: `#${label.color}` }}\n >\n {label.name}\n </span>\n </a>\n ))}\n </div>\n ) : null;\n\n return (\n <AttractableDiv className=\"card\">\n {header}\n <div\n className=\"card-body d-flex flex-column gap-3\"\n style={{ fontSize: 15 }}\n >\n {titleArea}\n {labelList}\n </div>\n </AttractableDiv>\n );\n};\n\nreturn GithubKanbanTicket(props);\n" }, "devhub.entity.post.Post": { "": "// Ideally, this would be a page\n\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\nconst { getDepositAmountForWriteAccess } = VM.require(\n \"devhub.megha19.near/widget/core.lib.common\"\n);\n\ngetDepositAmountForWriteAccess || (getDepositAmountForWriteAccess = () => {});\nconst { draftState, onDraftStateChange } = VM.require(\n \"devhub.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.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.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"}\n props={{\n text: 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.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"}\n props={{\n text: 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.megha19.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.notification.Left": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nif (!props.type) {\n return \"Loading ...\";\n}\n\nconst type = props.type.split(\"/\")[1];\nreturn props.type ? (\n <>\n {type == \"like\"\n ? \"liked your\"\n : type == \"reply\"\n ? \"replied to your\"\n : type == \"edit\"\n ? \"edited your\"\n : type == \"mention\"\n ? \"mentioned you in their\"\n : type}\n <a\n className=\"fw-bold text-muted\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"proposal\",\n id: props.proposal,\n },\n })}\n >\n DevHub proposal\n </a>\n </>\n) : (\n \"Loading ...\"\n);\n" }, "devhub.components.molecule.DropDown": { "": "const options = props.options; // [{label:\"\",value:\"\"}]\nconst label = props.label;\nconst onUpdate = props.onUpdate ?? (() => {});\nconst selectedValue = props.selectedValue;\nconst [selected, setSelected] = useState(selectedValue);\n\nconst StyledDropdown = styled.div`\n .drop-btn {\n width: 100%;\n max-width: 200px;\n text-align: left;\n padding-inline: 10px;\n }\n\n .dropdown-item.active,\n .dropdown-item:active {\n background-color: #f0f0f0 !important;\n color: black;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n`;\n\nuseEffect(() => {\n onUpdate(selected);\n}, [selected]);\n\nreturn (\n <div>\n <div class=\"dropdown w-100\">\n <StyledDropdown>\n <button\n class=\"btn drop-btn text-truncate dropdown-toggle bg-white border rounded-2\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n >\n {label} {selected && label && \": \"} {selected.label}\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end dropdown-menu-lg-start px-2 shadow\">\n {options.map((item) => (\n <li\n style={{ borderRadius: \"5px\" }}\n class=\"dropdown-item cursor-pointer link-underline link-underline-opacity-0\"\n onClick={() => {\n if (selected.label !== item.label) {\n setSelected(item);\n }\n }}\n >\n {item.label}\n </li>\n ))}\n </ul>\n </StyledDropdown>\n </div>\n </div>\n);\n" }, "devhub.entity.addon.blog.Configurator": { "": "const { data, handle, onSubmit } = props;\n\nconst { Tile } =\n VM.require(\"devhub.megha19.near/widget/devhub.components.molecule.Tile\") ||\n (() => <></>);\n\nif (!Tile) {\n return <div>Loading...</div>;\n}\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n padding: 20px;\n`;\n\nconst Item = styled.div`\n padding: 10px;\n margin: 5px;\n display: flex;\n align-items: center;\n flex-direction: row;\n gap: 10px;\n`;\n\nconst EditableField = styled.input`\n flex: 1;\n`;\n\nconst initialData = data;\n\nreturn (\n <Tile className=\"p-3 bg-white\">\n <Container>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.addon.blog.editor.index\"}\n props={{\n data,\n handle,\n }}\n />\n </Container>\n </Tile>\n);\n" }, "core.lib.data-request": { "": "const DataRequest = {\n /**\n * Requests all the data from non-empty pages of the paginated API.\n *\n * **Notice: currently expected to work only with array responses.**\n *\n * @param {object} parameters\n * \tRequest parameters including the number of page to start with,\n * \tand an accumulated response buffer, if it exists.\n *\n * @param {array | null | undefined} parameters.buffer\n * @param {number} parameters.startWith\n *\n * @param {(pageNumber: number) => array} requestByNumber\n *\n * @returns {array} The final accumulated response.\n */\n paginated: (requestByNumber, { buffer, startWith }) => {\n const startPageNumber = startWith ?? 1,\n accumulatedResponse = buffer ?? [];\n\n const latestResponse = requestByNumber(startPageNumber) ?? [];\n\n if (latestResponse.length === 0) {\n return accumulatedResponse;\n } else {\n return DataRequest.paginated(requestByNumber, {\n buffer: [...accumulatedResponse, ...latestResponse],\n startWith: startPageNumber + 1,\n });\n }\n },\n};\n\nreturn { DataRequest };\n" }, "devhub.feature.post-search.by-author": { "": "const { getAllAuthors } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nif (!getAllAuthors) {\n return <p>Loading modules...</p>;\n}\n\nconst selectedAuthors = props.author ? [{ name: props.author }] : [];\n\nconst authors = getAllAuthors();\n\nif (authors === null) {\n return <div>Loading ...</div>;\n}\n\nconst onChange = (selectedAuthors) =>\n props.onAuthorSearch(selectedAuthors[0]?.name);\n\nreturn (\n <Typeahead\n clearButton\n id=\"basic-typeahead-single\"\n labelKey=\"name\"\n options={authors.map((author) => ({ name: author }))}\n placeholder=\"Search by author\"\n selected={selectedAuthors}\n {...{ onChange }}\n />\n);\n" }, "devhub.page.community.configuration": { "": "const { Tile } =\n VM.require(\"devhub.megha19.near/widget/devhub.components.molecule.Tile\") ||\n (() => <></>);\n\nif (!Tile) {\n return <div>Loading...</div>;\n}\n\nconst {\n permissions,\n handle,\n community,\n setCommunityAddons,\n deleteCommunity,\n updateCommunity,\n} = props;\n\nconst [communityData, setCommunityData] = useState(community || {});\nconst [selectedAddon, setSelectedAddon] = useState(null);\nconst [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);\n\nconst sectionSubmit = (sectionData) => {\n const updatedCommunityData = {\n ...Object.entries(sectionData).reduce(\n (update, [propertyKey, propertyValue]) => ({\n ...update,\n\n [propertyKey]:\n typeof propertyValue !== \"string\" || (propertyValue?.length ?? 0) > 0\n ? propertyValue ?? null\n : null,\n }),\n\n communityData\n ),\n };\n setCommunityData(updatedCommunityData);\n setHasUnsavedChanges(true);\n};\n\nconst hasConfigurePermissions = permissions.can_configure;\nconst hasDeletePermissions = permissions.can_delete;\n\nreturn (\n <div\n className=\"d-flex flex-column align-items-center gap-4 w-100 p-4\"\n style={{ maxWidth: 960, marginBottom: \"60px\" }}\n >\n <Tile className={\"bg-white\"}>\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.BrandingConfigurator\"\n }\n props={{\n onSubmit: sectionSubmit,\n data: communityData,\n hasConfigurePermissions,\n link: `/devhub.megha19.near/widget/app?page=community&handle=${handle}`,\n }}\n />\n </Tile>\n <Tile className={\"p-3 bg-white\"}>\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.ConfigurationSection\"\n }\n props={{\n title: \"Community Information\",\n hasConfigurePermissions,\n Configurator: (p) => (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.InformationConfigurator\"\n }\n props={{\n data: communityData,\n onSubmit: sectionSubmit,\n ...p,\n }}\n />\n ),\n }}\n />\n </Tile>\n <Tile className={\"p-3 bg-white\"}>\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.ConfigurationSection\"\n }\n props={{\n title: \"About\",\n hasConfigurePermissions,\n Configurator: (p) => (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.AboutConfigurator\"\n }\n props={{\n data: communityData,\n onSubmit: sectionSubmit,\n ...p,\n }}\n />\n ),\n }}\n />\n </Tile>\n <Tile className={\"p-3 bg-white\"}>\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.ConfigurationSection\"\n }\n props={{\n title: \"Community Admins\",\n hasConfigurePermissions,\n Configurator: (p) => (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.AccessControlConfigurator\"\n }\n props={{\n data: communityData,\n onSubmit: sectionSubmit,\n ...p,\n }}\n />\n ),\n }}\n />\n </Tile>\n {hasConfigurePermissions && (\n <Tile className={\"p-3 bg-white\"}>\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.ConfigurationSection\"\n }\n props={{\n title: \"Add-Ons\",\n hasConfigurePermissions,\n Configurator: (p) => (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.entity.community.configuration.AddonsConfigurator\"\n }\n props={{\n data: communityData.addons || [],\n onSubmit: (v) => setCommunityAddons({ handle, addons: v }),\n ...p,\n }}\n />\n ),\n }}\n />\n </Tile>\n )}\n {hasDeletePermissions && (\n <div\n className=\"d-flex justify-content-center gap-4 p-4 w-100\"\n style={{ maxWidth: 896 }}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-lg btn-outline-danger border-none\" },\n label: \"Delete community\",\n onClick: () => deleteCommunity({ handle }),\n }}\n />\n </div>\n )}\n {hasConfigurePermissions && hasUnsavedChanges && (\n <div\n className=\"position-fixed end-0 bottom-0 bg-transparent pe-4 pb-4\"\n style={{ borderTopLeftRadius: \"100%\" }}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-lg btn-success\" },\n icon: { type: \"svg_icon\", variant: \"floppy_drive\" },\n label: \"Save\",\n onClick: () =>\n updateCommunity({ handle, community: communityData }), // TODO : Track changes in State\n }}\n />\n </div>\n )}\n </div>\n);\n" }, "devhub.page.addon": { "": "const Container = styled.div`\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n position: relative;\n padding: 0 1rem;\n\n @media screen and (max-width: 960px) {\n padding: 0 1rem;\n }\n`;\n\nconst Content = styled.div`\n flex: 1;\n padding: 20px;\n overflow: auto;\n`;\n\nconst SettingsButton = styled.button`\n position: absolute;\n top: 10px;\n right: 10px;\n\n background-color: #fff;\n display: flex;\n padding: 14px 16px;\n align-items: center;\n gap: 16px;\n width: 50px;\n height: 50px;\n\n border-radius: 4px;\n border: 1px solid #00ec97;\n\n z-index: 10;\n\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n\n &:hover {\n transform: translateY(2px);\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\n }\n\n &:active {\n transform: translateY(0);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\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\nconst { addon, permissions, handle } = props;\n\nconst { getAllAddons, setCommunityAddon } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nif (!getAllAddons || !setCommunityAddon) {\n return <p>Loading modules...</p>;\n}\n\nconst availableAddons = getAllAddons();\n\nconst addonMatch = (availableAddons ?? []).find(\n (it) => it.id === addon.addon_id\n);\n\nif (!addonMatch) {\n return (\n <CenteredMessage height={\"384px\"}>\n <h2>Addon with id: \"{addon.addon_id}\" not found.</h2>\n </CenteredMessage>\n );\n}\n\nconst config = JSON.parse(addon.parameters || \"null\");\n\nconst ButtonRow = styled.div`\n display: flex;\n justify-content: space-between;\n`;\n\nconst [view, setView] = useState(props.view || \"viewer\");\n\nif (\"devhub.megha19.near\" !== \"devhub.near\") {\n addonMatch.configurator_widget = addonMatch.configurator_widget.replace(\n \"devhub.near/\",\n \"devhub.megha19.near/\"\n );\n addonMatch.view_widget = addonMatch.view_widget.replace(\n \"devhub.near/\",\n \"devhub.megha19.near/\"\n );\n}\n\nreturn (\n <Container>\n {permissions.can_configure && addonMatch.configurator_widget !== \"\" && (\n <SettingsButton\n onClick={() => setView(view === \"configure\" ? \"view\" : \"configure\")}\n >\n {view === \"configure\" ? (\n <span className=\"bi bi-x\"></span>\n ) : (\n <span className=\"bi bi-gear\"></span>\n )}\n </SettingsButton>\n )}\n <Content>\n {view === \"configure\" ? (\n <Widget\n src={addonMatch.configurator_widget}\n props={{\n ...config,\n data: config,\n onSubmit: (data) => {\n setCommunityAddon({\n handle,\n addon: {\n ...addon,\n parameters: JSON.stringify(data),\n },\n });\n },\n handle, // this is temporary prop drilling until kanban and github are migrated\n permissions,\n }}\n />\n ) : (\n <Widget\n src={addonMatch.view_widget}\n props={{\n ...config,\n data: config,\n handle,\n permissions,\n transactionHashes: props.transactionHashes,\n }}\n />\n )}\n </Content>\n </Container>\n);\n" }, "devhub.entity.proposal.VerificationStatus": { "": "const receiverAccount = props.receiverAccount;\nconst showGetVerifiedBtn = props.showGetVerifiedBtn;\nconst [verificationStatus, setVerificationStatus] = useState(null);\nconst imageSize = props.imageSize ?? 40;\n\nconst WarningImg =\n \"https://ipfs.near.social/ipfs/bafkreieq4222tf3hkbccfnbw5kpgedm3bf2zcfgzbnmismxav2phqdwd7q\";\n\nconst SuccessImg =\n \"https://ipfs.near.social/ipfs/bafkreidqveupkcc7e3rko2e67lztsqrfnjzw3ceoajyglqeomvv7xznusm\";\n\nuseEffect(() => {\n if (\n receiverAccount.length === 64 ||\n (receiverAccount ?? \"\").includes(\".near\")\n ) {\n useCache(\n () =>\n asyncFetch(\n `https://neardevhub-kyc-proxy.shuttleapp.rs/kyc/${receiverAccount}`\n ).then((res) => {\n let displayableText = \"\";\n switch (res.body.kyc_status) {\n case \"Approved\":\n displayableText = \"Verified\";\n break;\n case \"Pending\":\n displayableText = \"Pending\";\n break;\n default:\n displayableText = \"Not Verfied\";\n break;\n }\n setVerificationStatus(displayableText);\n }),\n \"ky-check-proposal\" + receiverAccount,\n { subscribe: false }\n );\n }\n}, [receiverAccount]);\n\nconst DropdowntBtnContainer = styled.div`\n font-size: 13px;\n min-width: 150px;\n\n .custom-select {\n position: relative;\n }\n\n .select-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n border: 1px solid #ccc;\n border-radius-top: 5px;\n cursor: pointer;\n background-color: #fff;\n border-radius: 5px;\n }\n\n .no-border {\n border: none !important;\n }\n\n .options-card {\n position: absolute;\n top: 100%;\n left: 0;\n width: 200%;\n border: 1px solid #ccc;\n background-color: #fff;\n padding: 0.5rem;\n z-index: 99;\n font-size: 13px;\n border-radius: 0.375rem !important;\n }\n\n .left {\n right: 0 !important;\n left: auto !important;\n }\n\n @media screen and (max-width: 768px) {\n .options-card {\n right: 0 !important;\n left: auto !important;\n }\n }\n\n .option {\n margin-block: 2px;\n padding: 5px;\n cursor: pointer;\n border-bottom: 1px solid #f0f0f0;\n transition: background-color 0.3s ease;\n border-radius: 0.375rem !important;\n }\n\n .option:hover {\n background-color: #f0f0f0; /* Custom hover effect color */\n }\n\n .option:last-child {\n border-bottom: none;\n }\n\n .selected {\n background-color: #f0f0f0;\n }\n\n .disabled {\n background-color: #f4f4f4 !important;\n cursor: not-allowed !important;\n font-weight: 500;\n color: #b3b3b3;\n }\n\n .disabled .circle {\n opacity: 0.5;\n }\n\n .circle {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n }\n\n .grey {\n background-color: #818181;\n }\n\n .green {\n background-color: #04a46e;\n }\n\n a:hover {\n text-decoration: none;\n }\n\n .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n`;\n\nconst [kycOptionsOpen, setKycOptions] = useState(false);\n\nconst VerificationBtn = () => {\n const btnOptions = [\n {\n src: \"https://ipfs.near.social/ipfs/bafkreidqveupkcc7e3rko2e67lztsqrfnjzw3ceoajyglqeomvv7xznusm\",\n label: \"KYC\",\n description: \"Choose this if you are an individual.\",\n value: \"KYC\",\n },\n {\n src: \"https://ipfs.near.social/ipfs/bafkreic5ksax6b45pelvxm6a2v2j465jgbitpzrxtzpmn6zehl23gocwxm\",\n label: \"KYB\",\n description: \"Choose this if you are a business or corporate entity..\",\n value: \"KYB\",\n },\n ];\n\n const toggleDropdown = () => {\n setKycOptions(!kycOptionsOpen);\n };\n\n return (\n <DropdowntBtnContainer>\n <div\n className=\"custom-select\"\n tabIndex=\"0\"\n id=\"getVerifiedButton\"\n onClick={toggleDropdown}\n onBlur={() => {\n setTimeout(() => {\n setKycOptions(false);\n }, 100);\n }}\n >\n <div\n className={\n \"select-header no-border black-btn btn d-inline-flex align-items-center gap-2\"\n }\n >\n <div className=\"d-flex align-items-center gap-1\">\n Get Verified\n <i class=\"bi bi-box-arrow-up-right\"></i>\n </div>\n </div>\n\n {kycOptionsOpen && (\n <div className=\"options-card left\">\n {btnOptions.map((option) => (\n <a\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n href={\n option.value === \"KYC\"\n ? \"https://go.fractal.id/near-social-kyc\"\n : \"https://go.fractal.id/near-social-kyb\"\n }\n >\n <div\n key={option.value}\n className={`option ${\n selectedOption.value === option.value ? \"selected\" : \"\"\n }`}\n >\n <div className={`d-flex gap-2 align-items-center`}>\n <img src={option.src} height={30} />\n <div>\n <div className=\"fw-bold\">{option.label}</div>\n <div className=\"text-muted text-xs\">\n {option.description}\n </div>\n </div>\n </div>\n </div>\n </a>\n ))}\n </div>\n )}\n </div>\n </DropdowntBtnContainer>\n );\n};\n\nreturn (\n <div>\n <div className=\"d-flex text-black justify-content-between align-items-center\">\n <div className=\"d-flex\" style={{ gap: \"12px\" }}>\n <img\n className=\"align-self-center object-fit-cover\"\n src={verificationStatus === \"Verified\" ? SuccessImg : WarningImg}\n height={imageSize}\n />\n <div className=\"d-flex flex-column justify-content-center\">\n <div className=\"h6 mb-0\">Fractal</div>\n <div className=\"text-sm text-muted\">{verificationStatus}</div>\n </div>\n </div>\n {verificationStatus !== \"Verified\" && showGetVerifiedBtn && (\n <VerificationBtn />\n )}\n </div>\n </div>\n);\n" }, "devhub.components.island.connect": { "": "const { getFeaturedCommunities } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nif (!getFeaturedCommunities) {\n return <p>Loading modules...</p>;\n}\n\nconst communities = getFeaturedCommunities();\n\nif (!communities) {\n return <p>Loading communities...</p>;\n}\n\nconst [startIndex, setStartIndex] = useState(0);\nconst [endIndex, setEndIndex] = useState(2);\n\nconst DescriptionHeader = styled.h2`\n color: #f4f4f4;\n font-size: 1.75rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 43.2px */\n\n @media screen and (max-width: 786px) {\n font-size: 1.5rem;\n }\n`;\n\nconst Description = styled.p`\n color: #f4f4f4;\n text-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n\n @media screen and (max-width: 786px) {\n font-size: 1rem;\n }\n`;\n\nconst imageSource =\n \"https://ipfs.near.social/ipfs/bafkreic7wxhocbnxoo63uh6n2ur3otykbzouymobt3ebgd2b4dmdiu3764\";\n\nconst CardBody = styled.div`\n border-radius: 1rem;\n border: 1px solid #00ec97;\n background: #3f4040;\n\n display: flex;\n max-width: 31.5%;\n height: 12rem;\n padding: 1.5rem;\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n gap: 0.5rem;\n flex-shrink: 0;\n align-self: stretch;\n\n h3 {\n color: #00ec97;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 40px */\n }\n\n p {\n color: #818181;\n font-size: 1.125rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n }\n\n a {\n color: #00ec97;\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n\n &:hover {\n text-decoration: none;\n }\n }\n\n @media screen and (max-width: 768px) {\n max-width: 80%;\n }\n`;\n\nconst Card = ({ title, description, href }) => {\n return (\n <CardBody>\n <h3>{title}</h3>\n <p>{description}</p>\n <a href={href}>Learn more →</a>\n </CardBody>\n );\n};\n\nconst Cards = communities.map((com) => {\n return {\n title: com.name,\n description: com.description,\n href: \"/devhub.megha19.near/widget/app?page=community&handle=\" + com.handle,\n };\n});\n\nconst ForwardButton = styled.button`\n all: unset;\n position: absolute;\n right: 0;\n\n margin: 1rem;\n\n &:hover,\n &:active {\n border: none;\n outline: none;\n }\n\n ${endIndex >= Cards.length - 1 && \"svg {transform: rotate(180deg);}\"}\n`;\n\nconst handleForward = () => {\n if (endIndex <= Cards.length - 1) {\n setStartIndex(endIndex + 1);\n setEndIndex(endIndex + 3);\n } else {\n setStartIndex(0);\n setEndIndex(2);\n }\n};\n\nconst CTA = styled.a`\n color: #00ec97 !important;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n\n @media screen and (max-width: 768px) {\n font-size: 1.25rem;\n }\n`;\n\nconst Subheading = styled.h3`\n color: #8a8e93;\n font-size: 2.25rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 43.2px */\n\n padding: 3rem;\n padding-top: 0;\n\n @media screen and (max-width: 786px) {\n padding: 1rem;\n padding-top: 0;\n font-size: 1.5rem;\n }\n`;\n\nconst Container = styled.div`\n width: 100%;\n display: flex;\n position: relative;\n align-items: center;\n\n @media screen and (max-width: 768px) {\n flex-direction: column;\n }\n`;\n\nconst DescriptionContainer = styled.div`\n padding: 3rem;\n width: 55%;\n\n @media screen and (max-width: 768px) {\n padding: 1rem;\n width: 100%;\n }\n`;\n\nconst ImageContainer = styled.div`\n position: absolute;\n top: 25%;\n right: 0;\n\n width: 50%;\n height: 65%;\n\n @media screen and (max-width: 768px) {\n position: relative;\n padding: 0 1rem;\n\n height: 225px;\n width: 100%;\n }\n`;\n\nconst Image = styled.img`\n width: 100%;\n height: 90%;\n object-fit: cover;\n clip-path: polygon(15% 0, 100% 0%, 100% 100%, 0% 100%);\n\n @media screen and (max-width: 768px) {\n clip-path: none;\n }\n`;\n\nconst CardsContainer = styled.div`\n padding: 3rem;\n padding-top: 0;\n\n position: relative;\n display: flex;\n flex-direction: row;\n gap: 1rem;\n width: 100%;\n align-items: center;\n\n @media screen and (max-width: 768px) {\n display: none;\n }\n`;\n\nconst ArrowIcon = () => {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"48\"\n height=\"49\"\n viewBox=\"0 0 48 49\"\n fill=\"none\"\n >\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M32.9999 24.5L17.9999 39.5L15.8999 37.4L28.7999 24.5L15.8999 11.6L17.9999 9.5L32.9999 24.5Z\"\n fill=\"#00EC97\"\n />\n </svg>\n );\n};\n\nconst CTAContainer = styled.div`\n padding: 3rem;\n padding-top: 0;\n\n @media screen and (max-width: 786px) {\n padding: 1rem;\n padding-top: 0;\n }\n`;\n\nconst MobileCards = styled.div`\n display: none;\n\n @media screen and (max-width: 768px) {\n display: flex;\n padding: 1rem;\n flex-direction: row;\n overflow-x: auto;\n gap: 1rem;\n }\n`;\n\nconst Content = (\n <>\n <Container>\n <DescriptionContainer>\n <DescriptionHeader>\n Communities are the lifeblood of /dev/hub\n </DescriptionHeader>\n <Description>\n We believe that communities are the foundation of a decentralized\n ecosystem. Explore and engage with our diverse range of communities\n today.\n </Description>\n </DescriptionContainer>\n <ImageContainer>\n <Image src={imageSource} />\n </ImageContainer>\n </Container>\n <Subheading>Featured Communities</Subheading>\n <CardsContainer>\n {Cards.slice(startIndex, endIndex + 1).map((card, idx) => (\n <Card\n title={card.title}\n description={card.description}\n href={card.href}\n key={`project-card-${idx}`}\n />\n ))}\n <ForwardButton onClick={handleForward}>\n <ArrowIcon />\n </ForwardButton>\n </CardsContainer>\n <MobileCards>\n {Cards.map((card, idx) => (\n <Card\n title={card.title}\n description={card.description}\n href={card.href}\n key={`mobile-card-${idx}`}\n />\n ))}\n </MobileCards>\n <CTAContainer>\n <CTA href=\"/devhub.megha19.near/widget/app?page=communities\">\n Explore all communities →\n </CTA>\n </CTAContainer>\n </>\n);\n\nreturn (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.island.home-section\"\n props={{\n title: \"/connect\",\n children: Content,\n background: true,\n }}\n />\n);\n" }, "core.lib.uuid": { "": "const uuid = () =>\n [Date.now().toString(16)]\n .concat(\n Array.from(\n { length: 4 },\n () => Math.floor(Math.random() * 0xffffffff) & 0xffffffff\n ).map((value) => value.toString(16))\n )\n .join(\"-\");\n\nconst withUUIDIndex = (data) => {\n const id = uuid();\n\n return Object.fromEntries([[id, { ...data, id }]]);\n};\n\nreturn { uuid, withUUIDIndex };\n" }, "devhub.components.island.home-section": { "": "const title = props.title;\nconst titleColor = props.titleColor;\nconst description = props.description;\nconst children = props.children;\nconst background = props.background;\n\nconst Section = styled.div`\n ${background && \"background: #292929;\"}\n ${background && \"color: #F4F4F4;\"}\n width: 100%;\n`;\n\nconst SectionHeader = styled.h2`\n color: ${titleColor || \"#00ec97\"};\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.24px;\n\n @media screen and (max-width: 768px) {\n font-size: 1.25rem;\n }\n`;\n\nconst SectionDescription = styled.h3`\n color: #151515;\n font-size: 2.25rem;\n font-style: normal;\n font-weight: 700;\n line-height: 110%; /* 39.6px */\n letter-spacing: -0.72px;\n\n margin-bottom: 2.25rem;\n margin-top: 2.25rem;\n\n max-width: 40rem;\n\n @media screen and (max-width: 768px) {\n margin-bottom: 1.5rem;\n margin-top: 1.5rem;\n font-size: 1.5rem;\n }\n`;\n\nconst Container = styled.div`\n padding: 3rem;\n padding-bottom: 0;\n\n @media screen and (max-width: 768px) {\n padding: 1rem;\n }\n`;\n\nreturn (\n <Section>\n <Container>\n <SectionHeader>{title}</SectionHeader>\n {description && <SectionDescription>{description}</SectionDescription>}\n </Container>\n {children}\n </Section>\n);\n" }, "devhub.page.community.index": { "": "const { normalize } = VM.require(\"devhub.megha19.near/widget/core.lib.stringUtils\");\n\nnormalize || (normalize = () => {});\n\nconst Button = styled.button`\n height: 40px;\n font-size: 14px;\n border-color: #e3e3e0;\n background-color: #ffffff;\n`;\n\nconst Banner = styled.div`\n max-width: 100%;\n min-height: 240px;\n height: 240px;\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\nconst NavUnderline = styled.ul`\n cursor: pointer;\n a {\n color: #151515;\n text-decoration: none;\n }\n\n a.active {\n font-weight: bold;\n border-bottom: 4px solid #00ec97;\n }\n border-bottom: 1px solid #cccccc;\n`;\n\nconst { tab, permissions, community, view } = props;\n\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nif (!href) {\n return <></>;\n}\n\nif (!tab) {\n tab = \"Announcements\";\n}\n\ntab = normalize(tab);\n\nconst [isLinkCopied, setLinkCopied] = useState(false);\n\nconst tabs = [];\n\n(community.addons || []).map((addon) => {\n addon.enabled &&\n tabs.push({\n title: addon.display_name,\n view: \"devhub.megha19.near/widget/devhub.page.addon\",\n params: {\n addon,\n handle: community.handle,\n transactionHashes: props.transactionHashes,\n },\n });\n});\n\nconst onShareClick = () =>\n clipboard\n .writeText(\n href({\n gateway: \"near.social\",\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"community\", handle: community.handle },\n })\n )\n .then(setLinkCopied(true));\n\nlet currentTab = tabs.find((it) => normalize(it.title) === tab);\n\nconst CommunityName = styled.span`\n color: #151515;\n font-size: 2.25rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 48px */\n\n @media screen and (max-width: 768px) {\n font-size: 1.5rem;\n }\n`;\n\nconst CommunityDetails = styled.span`\n color: #818181;\n font-size: 1rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n`;\n\nfunction trimHttps(url) {\n if (url.startsWith(\"https://\")) {\n return url.substring(8);\n }\n return url;\n}\n\n// some communities have url as handle (eg: devhub platform) while others has correct handle\nfunction checkTelegramHandle(tg) {\n const pattern = /https:\\/\\/t.me\\/(.*)/;\n const includesHttp = tg.match(pattern);\n const handle = includesHttp ? includesHttp[1] : tg;\n return { handle, url: \"https://t.me/\" + handle };\n}\n\nconst socialLinks = [\n ...((community.website_url?.length ?? 0) > 0\n ? [\n {\n href: `https://${trimHttps(community.website_url)}`,\n iconClass: \"bi bi-globe\",\n name: trimHttps(community.website_url),\n },\n ]\n : []),\n\n ...((community.github_handle?.length ?? 0) > 0\n ? [\n {\n href: `https://github.com/${community.github_handle}`,\n iconClass: \"bi bi-github\",\n name: community.github_handle,\n },\n ]\n : []),\n\n ...((community.twitter_handle?.length ?? 0) > 0\n ? [\n {\n href: `https://twitter.com/${community.twitter_handle}`,\n iconClass: \"bi bi-twitter\",\n name: community.twitter_handle,\n },\n ]\n : []),\n\n ...(community.telegram_handle?.length > 0\n ? [\n {\n href: checkTelegramHandle(community.telegram_handle).url,\n iconClass: \"bi bi-telegram\",\n name: checkTelegramHandle(community.telegram_handle).handle,\n },\n ]\n : []),\n];\n\nconst NavlinksContainer = styled.div`\n //background: white;\n padding: 0 3rem;\n\n @media screen and (max-width: 960px) {\n padding: 0 1rem;\n }\n`;\n\nreturn (\n <div\n className=\"d-flex flex-column gap-3 w-100\"\n style={{ background: \"#F4F4F4\" }}\n >\n <Banner\n className=\"object-fit-cover\"\n style={{\n background: `center / cover no-repeat url(${community.banner_url})`,\n }}\n />\n\n <div className=\"container d-flex flex-wrap justify-content-between align-items-center align-items-md-start gap-4\">\n <div className=\"d-flex flex-column ms-3\">\n <div className=\"position-relative\">\n <div style={{ width: 150, height: 45 }}>\n <img\n alt=\"Loading logo...\"\n className=\"rounded-circle position-absolute\"\n width=\"160\"\n height=\"160\"\n src={community.logo_url}\n style={{ top: -124 }}\n />\n </div>\n </div>\n\n <div className=\"d-flex flex-column gap-3 ps-md-3 pt-md-3 pb-md-2\">\n <CommunityName className=\"text-nowrap\">\n {community.name}\n </CommunityName>\n <CommunityDetails>{community.description}</CommunityDetails>\n </div>\n\n <div className=\"mt-3 ps-3 d-flex gap-3 align-items-center\">\n {socialLinks.map((link, index) => (\n <a\n href={link.href}\n style={{\n marginLeft: index !== 0 ? \"0px\" : \"0px\",\n color: \"#818181\",\n }}\n key={link.href}\n target=\"_blank\"\n >\n <i className={link.iconClass}></i>\n </a>\n ))}\n </div>\n </div>\n\n <div className=\"d-flex align-items-end gap-3 ms-auto mb-md-5 me-4\">\n {permissions.can_configure && (\n <Link\n to={`/devhub.megha19.near/widget/app?page=community.configuration&handle=${community.handle}`}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-light text-dark shadow-none\" },\n notRounded: true,\n style: {\n display: \"flex\",\n padding: \"0.75rem 1rem\",\n alignItems: \"center\",\n gap: \"16px\",\n\n borderRadius: \"4px\",\n border: \"1px solid #00EC97\",\n background: \"rgba(129, 129, 129, 0.00)\",\n },\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-gear-wide-connected\",\n },\n label: \"Configure community\",\n }}\n />\n </Link>\n )}\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-light text-dark shadow-none\" },\n notRounded: true,\n style: {\n display: \"flex\",\n padding: \"0.75rem 1rem\",\n alignItems: \"center\",\n gap: \"16px\",\n\n borderRadius: \"4px\",\n border: \"1px solid #00EC97\",\n background: \"rgba(129, 129, 129, 0.00)\",\n },\n label: \"Share ↗\",\n onClick: onShareClick,\n onMouseLeave: () => setLinkCopied(false),\n title: \"Copy link to clipboard\",\n }}\n />\n </div>\n </div>\n <NavlinksContainer>\n <NavUnderline className=\"nav gap-4 my-4\">\n {tabs.map(\n ({ title }) =>\n title && (\n <li className=\"nav-item\" key={title}>\n <Link\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"community\",\n handle: community.handle,\n tab: normalize(title),\n },\n })}\n aria-current={tab === normalize(title) && \"page\"}\n className={[\n \"d-inline-flex gap-2\",\n tab === normalize(title) ? \"nav-link active\" : \"nav-link\",\n ].join(\" \")}\n >\n <span>{title}</span>\n </Link>\n </li>\n )\n )}\n </NavUnderline>\n {currentTab.title === \"Activity\" && (\n <div\n className=\"my-4 d-flex align-items-center justify-content-between\"\n style={{ gap: \"2.5rem\" }}\n >\n <div class=\"d-flex align-items-center justify-content-between\">\n <small class=\"text-muted\">\n <span>Required tags:</span>\n <Link\n to={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"feed\", tag: community.tag },\n })}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.atom.Tag\"}\n props={{\n tag: community.tag,\n }}\n />\n </Link>\n </small>\n </div>\n {context.accountId && (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.components.molecule.PostControls\"\n }\n props={{\n title: \"Post\",\n href: href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"create\",\n labels: [community.tag],\n },\n }),\n }}\n />\n )}\n </div>\n )}\n </NavlinksContainer>\n {currentTab && (\n <div className=\"d-flex w-100 h-100\" key={currentTab.title}>\n <Widget\n src={currentTab.view}\n props={{\n ...currentTab.params,\n view, // default view for an addon, can come as a prop from a community or from a direct link to page.addon\n\n // below is temporary prop drilling until kanban and github are migrated\n permissions,\n handle: community.handle,\n }}\n />\n </div>\n )}\n </div>\n);\n" }, "devhub.entity.addon.kanban.post_board": { "": "const { getPostsByLabel } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\nconst { getPost } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\ngetPost || (getPost = () => {});\ngetPostsByLabel || (getPostsByLabel = () => {});\n\nconst postTagsToIdSet = (tags) => {\n return new Set(\n (tags ?? [])?.map((tag) => getPostsByLabel({ label: tag }) ?? []).flat(1)\n );\n};\n\nconst sortByValues = {\n descendingAmount: \"descending-amount\",\n ascendingAmount: \"ascending-amount\",\n descendingDate: \"descending-date\",\n ascendingDate: \"ascending-date\",\n ascendingAuthor: \"ascending-author\",\n descendingAuthor: \"descending-author\",\n ascendingSponsor: \"ascending-sponsor\",\n descendingSponsor: \"descending-sponsor\",\n descendingLikes: \"descending-likes\",\n ascendingLikes: \"ascending-likes\",\n};\n\nconst configToColumnData = ({ columns, tags }) =>\n Object.entries(columns).reduce((registry, [columnId, column]) => {\n const postIds = (getPostsByLabel({ label: column.tag }) ?? []).reverse();\n return {\n ...registry,\n [columnId]: {\n ...column,\n postIds: postIds,\n },\n };\n }, {});\n\nconst basicAlphabeticalComparison = (a, b) => {\n if (a < b) return -1;\n if (a > b) return 1;\n return 0;\n};\n\nconst KanbanPostBoard = ({ metadata, payload }) => {\n const boardData = Object.entries(configToColumnData(payload) ?? {});\n\n const view = boardData.map(([columnId, column]) => {\n const data = [];\n column.postIds?.map((postId) => {\n if (postId) {\n const postData = getPost({\n post_id: postId ? parseInt(postId) : 0,\n });\n data.push(postData);\n }\n });\n\n // sort data by selected sorting mechanism\n switch (metadata.ticket.sortBy) {\n case sortByValues.descendingAmount:\n data.sort((a, b) => b?.snapshot?.amount - a?.snapshot?.amount);\n break;\n case sortByValues.ascendingAmount:\n data.sort((a, b) => a?.snapshot?.amount - b?.snapshot?.amount);\n break;\n case sortByValues.descendingDate:\n data.sort(\n (a, b) =>\n parseInt(b?.snapshot?.timestamp) - parseInt(a?.snapshot?.timestamp)\n );\n break;\n case sortByValues.ascendingDate:\n data.sort(\n (a, b) =>\n parseInt(a?.snapshot?.timestamp) - parseInt(b?.snapshot?.timestamp)\n );\n break;\n case sortByValues.ascendingAuthor:\n data.sort((a, b) =>\n basicAlphabeticalComparison(a.author_id, b.author_id)\n );\n break;\n case sortByValues.descendingAuthor:\n data.sort((a, b) =>\n basicAlphabeticalComparison(b.author_id, a.author_id)\n );\n break;\n case sortByValues.ascendingSponsor:\n data.sort((a, b) =>\n basicAlphabeticalComparison(\n a?.snapshot?.requested_sponsor || a?.snapshot?.supervisor,\n b?.snapshot?.requested_sponsor || b?.snapshot?.supervisor\n )\n );\n break;\n case sortByValues.descendingSponsor:\n data.sort((a, b) =>\n basicAlphabeticalComparison(\n b?.snapshot?.requested_sponsor || b?.snapshot?.supervisor,\n a?.snapshot?.requested_sponsor || a?.snapshot?.supervisor\n )\n );\n break;\n case sortByValues.descendingLikes:\n data.sort((a, b) => b.likes.length - a.likes.length);\n break;\n case sortByValues.ascendingLikes:\n data.sort((a, b) => a.likes.length - b.likes.length);\n break;\n default:\n data;\n break;\n }\n\n return (\n <div\n className=\"col-3\"\n style={{ minWidth: \"300px\" }}\n key={`column-${columnId}-view`}\n >\n <div className=\"card rounded-4\">\n <div\n className={[\n \"card-body d-flex flex-column gap-3 p-2\",\n \"border border-1 rounded-4\",\n ].join(\" \")}\n style={{ height: \"75vh\" }}\n >\n <span className=\"d-flex flex-column py-1\">\n <h6 className=\"card-title h6 d-flex align-items-center gap-2 m-0\">\n {column.title}\n\n <span className=\"badge rounded-pill bg-secondary\">\n {column.postIds.length}\n </span>\n </h6>\n\n <p class=\"text-secondary m-0\">{column.description}</p>\n </span>\n\n <div\n class=\"d-flex flex-column gap-2\"\n style={{ overflow: \"scroll\" }}\n >\n {data.length === column.postIds.length &&\n data.map((postData) => (\n <Widget\n src={`devhub.megha19.near/widget/devhub.entity.addon.${metadata.ticket.type}`}\n props={{\n metadata: { id: postData.postId, ...metadata.ticket },\n\n data: postData,\n }}\n key={postData.postId}\n />\n ))}\n </div>\n </div>\n </div>\n </div>\n );\n });\n\n return (\n <div>\n <div className=\"d-flex flex-column align-items-center gap-2 pb-4 w-100\">\n <h5 className=\"h4 d-inline-flex gap-2 m-0\">\n <span>{metadata?.title}</span>\n </h5>\n\n <p className=\"h6 m-0 py-1 text-secondary text-center\">\n {metadata?.description}\n </p>\n </div>\n <div className=\"d-flex gap-3 w-100\" style={{ overflow: \"scroll\" }}>\n <div\n className={[\n \"d-flex align-items-center justify-content-center w-100 text-black-50 opacity-50\",\n columns.length === 0 ? \"\" : \"d-none\",\n ].join(\" \")}\n style={{ height: 384 }}\n >\n No columns were created so far.\n </div>\n <span className={\"d-flex gap-3 w-100\"}>{view}</span>\n </div>\n </div>\n );\n};\n\nreturn KanbanPostBoard(props);\n" }, "devhub.entity.proposal.LinkedProposalsDropdown": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\nhref || (href = () => {});\n\nconst linkedProposals = props.linkedProposals;\nconst onChange = props.onChange;\nconst [selectedProposals, setSelectedProposals] = useState(linkedProposals);\nconst [proposalsOptions, setProposalsOptions] = useState([]);\nconst [searchProposalId, setSearchProposalId] = useState(\"\");\nconst QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql`;\nconst queryName =\n \"thomasguntenaar_near_devhub_proposals_quebec_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\nuseEffect(() => {\n if (JSON.stringify(linkedProposals) !== JSON.stringify(selectedProposals)) {\n setSelectedProposals(linkedProposals);\n }\n}, [linkedProposals]);\n\nuseEffect(() => {\n if (JSON.stringify(linkedProposals) !== JSON.stringify(selectedProposals)) {\n onChange(selectedProposals);\n }\n}, [selectedProposals]);\n\nfunction 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\nconst buildWhereClause = () => {\n let where = {};\n const { number, text } = separateNumberAndText(searchProposalId);\n\n if (number) {\n where = { proposal_id: { _eq: number }, ...where };\n }\n\n if (text) {\n where = { name: { _ilike: `%${text}%` }, ...where };\n }\n\n return where;\n};\n\nfunction fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": `thomasguntenaar_near` },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n}\n\nconst fetchProposals = () => {\n const FETCH_LIMIT = 30;\n const variables = {\n limit: FETCH_LIMIT,\n offset: 0,\n where: buildWhereClause(),\n };\n fetchGraphQL(query, \"GetLatestSnapshot\", variables).then(async (result) => {\n if (result.status === 200) {\n if (result.body.data) {\n const proposalsData =\n result.body.data\n .thomasguntenaar_near_devhub_proposals_quebec_proposals_with_latest_snapshot;\n\n const data = [];\n for (const prop of proposalsData) {\n data.push({\n label: \"# \" + prop.proposal_id + \" : \" + prop.name,\n value: prop.proposal_id,\n });\n }\n setProposalsOptions(data);\n }\n }\n });\n};\n\nuseEffect(() => {\n fetchProposals();\n}, [searchProposalId]);\n\nreturn (\n <>\n {selectedProposals.map((proposal) => {\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <a\n className=\"text-decoration-underline flex-1\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"proposal\",\n id: proposal.value,\n },\n })}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {proposal.label}\n </a>\n <div\n className=\"cursor-pointer\"\n onClick={() => {\n const updatedLinkedProposals = selectedProposals.filter(\n (item) => item.value !== proposal.value\n );\n setSelectedProposals(updatedLinkedProposals);\n }}\n >\n <i class=\"bi bi-trash3-fill\"></i>\n </div>\n </div>\n );\n })}\n\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.DropDownWithSearch\"\n props={{\n selectedValue: \"\",\n onChange: (v) => {\n if (!selectedProposals.some((item) => item.value === v.value)) {\n setSelectedProposals([...selectedProposals, v]);\n }\n },\n options: proposalsOptions,\n showSearch: true,\n searchInputPlaceholder: \"Search by Id\",\n defaultLabel: \"Search proposals\",\n searchByValue: true,\n onSearch: (value) => {\n setSearchProposalId(value);\n },\n }}\n />\n </>\n);\n" }, "devhub.feature.post-search.panel": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nhref || (href = () => {});\n\nState.init({\n tag: props.tag,\n author: props.author,\n term: \"\",\n});\n\nconst updateInput = (term) => {\n State.update({\n term: term,\n });\n};\n\nconst buttonStyle = {\n backgroundColor: \"#0C7283\",\n color: \"#f3f3f3\",\n};\n\nconst PageTitle = styled.h1`\n color: #555555;\n font-size: 24px;\n font-style: normal;\n font-weight: 500;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.24px;\n\n margin: 0;\n margin-bottom: 1rem;\n`;\n\nconst Container = styled.div`\n padding: 24px;\n width: 100%;\n`;\n\nconst PostContainer = styled.div`\n margin: 0 1rem;\n`;\n\nconst InputContainer = styled.div`\n display: flex;\n flex-direction: row;\n position: relative;\n width: 25%;\n\n @media screen and (max-width: 768px) {\n width: 100%;\n }\n`;\n\nconst DropdownContainer = styled.div`\n width: 25%;\n\n @media screen and (max-width: 768px) {\n width: 100%;\n }\n`;\n\nconst StyledDropdown = styled.div`\n button {\n width: 100%;\n text-align: left;\n\n &::after {\n position: absolute;\n right: 8px;\n top: 45%;\n transform: translateX(-50%);\n }\n }\n`;\n\nconst BannerWrapper = styled.div`\n background-color: #ffd2d2;\n .text-sm {\n font-size: 13px;\n }\n`;\n\nreturn (\n <>\n {!props.hideHeader && (\n <Container>\n <div className=\"w-100\">\n <BannerWrapper className=\"d-flex gap-3 align-items-center mb-4 p-3 rounded-3\">\n <div>\n <i class=\"bi bi-exclamation-triangle-fill\"></i>\n </div>\n <div>\n <div className=\"fw-bold\">This page is now archived! </div>\n <div className=\"text-sm\">\n For submitting formal funding proposals from DevDAO, please\n visit the new{\" \"}\n <a\n href=\"https://near.org/devhub.near/widget/app?page=proposals\"\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Proposal Feed\n </a>\n . To brainstorm and share ideas, please visit the relevant{\" \"}\n <a\n href=\"https://near.org/devhub.near/widget/app?page=communities\"\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n communities\n </a>\n .\n </div>\n </div>\n </BannerWrapper>\n <PageTitle>Activity Feed</PageTitle>\n <div>\n <div className=\"d-flex flex-column flex-md-row gap-4\">\n <InputContainer>\n <div className=\"position-absolute d-flex ps-3 flex-column h-100 justify-center\">\n <i class=\"bi bi-search m-auto\"></i>\n </div>\n <input\n type=\"search\"\n className=\"ps-5 form-control border rounded-2\"\n value={state.term ?? \"\"}\n onChange={(e) => updateInput(e.target.value)}\n onKeyDown={(e) => e.key == \"Enter\" && search()}\n placeholder={props.placeholder ?? `Search by content`}\n />\n </InputContainer>\n <DropdownContainer>\n <div class=\"dropdown\">\n <StyledDropdown>\n <button\n class=\"btn dropdown-toggle bg-white border rounded-2\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n >\n Sort: {props.recency === \"all\" ? \"All replies\" : \"Latest\"}{\" \"}\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end dropdown-menu-lg-start px-2 shadow\">\n <li>\n <a\n style={{ borderRadius: \"5px\" }}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"feed\" },\n })}\n >\n Latest\n </a>\n </li>\n <li>\n <a\n style={{ borderRadius: \"5px\" }}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"feed\", recency: \"all\" },\n })}\n >\n All replies\n </a>\n </li>\n </ul>\n </StyledDropdown>\n </div>\n </DropdownContainer>\n <div class=\"dropdown\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.feature.post-search.by-author\"\n props={{\n author: state.author,\n onAuthorSearch: (author) => {\n State.update({ author });\n },\n }}\n />\n </div>\n <div>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.feature.post-search.by-tag\"\n props={{\n tag: state.tag,\n onTagSearch: (tag) => {\n State.update({ tag });\n },\n }}\n />\n </div>\n <div className=\"d-flex flex-row-reverse flex-grow-1\">\n {props.children}\n </div>\n </div>\n </div>\n </div>\n </Container>\n )}\n <PostContainer>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.post.List\"\n props={{\n author: state.author,\n tag: state.tag,\n term: state.term,\n recency: props.recency,\n transactionHashes: props.transactionHashes,\n updateTagInput: (tag) => State.update({ tag }),\n }}\n />\n </PostContainer>\n </>\n);\n" }, "devhub.components.molecule.ProfileCard": { "": "const MutedText = styled.span`\n color: #818181;\n\n font-size: 16px;\n font-style: normal;\n font-weight: 400;\n line-height: 20px; /* 125% */\n`;\n\nconst AccountName = styled.span`\n color: #818181;\n font-size: 16px;\n font-style: normal;\n font-weight: 500;\n line-height: 20px;\n\n max-width: 30ch;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n`;\n\nconst ProfileCard = (props) => {\n const accountId = props.accountId ?? context.accountId;\n const link = props.link ?? true;\n // const hideAccountId = props.hideAccountId;\n // const hideName = props.hideName;\n const hideImage = props.hideImage;\n const iconOnly = props.iconOnly;\n const openLinkInNewTab = props.openLinkInNewTab ?? false;\n\n const profile = props.profile ?? Social.getr(`${accountId}/profile`);\n\n const name = profile.name ?? accountId;\n const title = props.title ?? `${name} @${accountId}`;\n const tooltip =\n props.tooltip && (props.tooltip === true ? title : props.tooltip);\n\n let inner = (\n <div className=\"d-flex flex-row justify-content-center align-items-center\">\n {!hideImage && (\n <Widget\n key=\"image\"\n src=\"mob.near/widget/ProfileImage\"\n props={{\n style: { width: \"2.5em\", height: \"2.5em\", marginRight: \"0.3em\" },\n profile,\n accountId,\n className: \"d-inline-block flex-shrink-0\",\n imageClassName: \"rounded-circle w-100 h-100 align-top\",\n }}\n />\n )}\n {!iconOnly && (\n <div className=\"d-flex flex-column gap-1\">\n <AccountName key=\"accountName\">{name}</AccountName>\n <AccountName key=\"accountId\">@{accountId}</AccountName>\n </div>\n )}\n </div>\n );\n\n inner = link ? (\n <a\n href={\n link !== true\n ? link\n : `/mob.near/widget/ProfilePage?accountId=${accountId}`\n }\n target={openLinkInNewTab ? \"_blank\" : \"\"}\n rel=\"noopener noreferrer\"\n className=\"link-dark text-truncate d-inline-flex\"\n >\n {inner}\n </a>\n ) : (\n <span className=\"text-truncate d-inline-flex\">{inner}</span>\n );\n\n if (props.tooltip === true) {\n return (\n <Widget\n src=\"mob.near/widget/Profile.OverlayTrigger\"\n props={{ accountId, children: inner }}\n />\n );\n }\n if (tooltip) {\n inner = (\n <OverlayTrigger placement=\"auto\" overlay={<Tooltip>{tooltip}</Tooltip>}>\n {inner}\n </OverlayTrigger>\n );\n }\n return (\n <div className=\"d-flex flex-row align-items-center\">\n {inner}\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.BadgesList\"\n props={{\n accountId,\n mode: \"compact\",\n }}\n />\n </div>\n );\n};\n\nreturn ProfileCard(props);\n" }, "devhub.entity.proposal.ComposeComment": { "": "const proposalId = props.id;\nconst draftKey = \"COMMENT_DRAFT\" + proposalId;\nconst draftComment = Storage.privateGet(draftKey);\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 }\n`;\nconst notifyAccountId = props.notifyAccountId;\nconst accountId = context.accountId;\nconst item = props.item;\nconst [allowGetDraft, setAllowGetDraft] = useState(true);\nconst [comment, setComment] = useState(null);\n\nuseEffect(() => {\n if (draftComment && allowGetDraft) {\n setComment(draftComment);\n setAllowGetDraft(false);\n }\n}, [draftComment]);\n\nuseEffect(() => {\n const handler = setTimeout(() => {\n if (comment !== draftComment) Storage.privateSet(draftKey, comment);\n }, 2000);\n\n return () => {\n clearTimeout(handler);\n };\n}, [comment, draftKey]);\n\nif (!accountId) {\n return (\n <div\n style={{\n marginLeft: 10,\n backgroundColor: \"#ECF8FB\",\n border: \"1px solid #E2E6EC\",\n }}\n className=\"d-flex align-items-center gap-1 p-4 rounded-2 flex-wrap flex-md-nowrap\"\n >\n <Link to=\"https://near.org/signup\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"grey-btn\" },\n label: \"Sign up\",\n }}\n />\n </Link>\n <div className=\"fw-bold\">to join this conversation.</div>\n <div>Already have an account?</div>\n <a className=\"text-decoration-underline\" href=\"https://near.org/signin\">\n Log in to comment\n </a>\n </div>\n );\n}\n\nfunction extractMentions(text) {\n const mentionRegex =\n /@((?:(?:[a-z\\d]+[-_])*[a-z\\d]+\\.)*(?:[a-z\\d]+[-_])*[a-z\\d]+)/gi;\n mentionRegex.lastIndex = 0;\n const accountIds = new Set();\n for (const match of text.matchAll(mentionRegex)) {\n if (\n !/[\\w`]/.test(match.input.charAt(match.index - 1)) &&\n !/[/\\w`]/.test(match.input.charAt(match.index + match[0].length)) &&\n match[1].length >= 2 &&\n match[1].length <= 64\n ) {\n accountIds.add(match[1].toLowerCase());\n }\n }\n return [...accountIds];\n}\n\nfunction extractTagNotifications(text, item) {\n return extractMentions(text || \"\")\n .filter((accountId) => accountId !== context.accountId)\n .map((accountId) => ({\n key: accountId,\n value: {\n type: \"mention\",\n item,\n },\n }));\n}\n\nfunction composeData() {\n const data = {\n post: {\n comment: JSON.stringify({\n type: \"md\",\n text: comment,\n item,\n }),\n },\n index: {\n comment: JSON.stringify({\n key: item,\n value: {\n type: \"md\",\n },\n }),\n },\n };\n\n const notifications = extractTagNotifications(comment, {\n type: \"social\",\n path: `${accountId}/post/comment`,\n });\n\n if (notifyAccountId && notifyAccountId !== context.accountId) {\n notifications.push({\n key: notifyAccountId,\n value: {\n type: \"devhub/reply\",\n item,\n proposal: proposalId,\n },\n });\n }\n\n if (notifications.length) {\n data.index.notify = JSON.stringify(\n notifications.length > 1 ? notifications : notifications[0]\n );\n }\n\n Social.set(data, {\n force: true,\n onCommit: () => {\n setComment(\"\");\n },\n onCancel: () => {},\n });\n}\n\nuseEffect(() => {\n if (props.transactionHashes && comment) {\n setComment(\"\");\n }\n}, [props.transactionHashes]);\n\nreturn (\n <div className=\"d-flex gap-2\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: accountId,\n }}\n />\n <div className=\"d-flex flex-column gap-2 w-100\">\n <b className=\"mt-1\">Add a comment</b>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Compose\"}\n props={{\n data: comment,\n onChange: setComment,\n autocompleteEnabled: true,\n placeholder: \"Add your comment here...\",\n height: \"160\",\n embeddCSS: ComposeEmbeddCSS,\n showProposalIdAutoComplete: true,\n }}\n />\n <div className=\"d-flex gap-2 align-content-center justify-content-end\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n label: \"Comment\",\n disabled: !comment,\n classNames: { root: \"green-btn btn-sm\" },\n onClick: () => {\n composeData();\n },\n }}\n />\n </div>\n </div>\n </div>\n);\n" }, "devhub.page.admin.moderatorsTab": { "": "const { accessControlInfo, createEditTeam } = props;\n\nconst { Tile } =\n VM.require(\"devhub.megha19.near/widget/devhub.components.molecule.Tile\") ||\n (() => <></>);\n\nif (!Tile) {\n return <div>Loading...</div>;\n}\n\nconst [editModerators, setEditModerators] = useState(false);\nconst [moderators, setModerators] = useState(\n accessControlInfo.members_list[\"team:moderators\"].children || []\n);\n\nconst handleEditModerators = () => {\n createEditTeam({\n teamName: \"moderators\",\n description:\n \"The moderator group has permissions to edit any posts and apply all labels, including restricted ones.\",\n label: \"*\",\n editPost: true,\n useLabels: true,\n members: moderators,\n contractCall: \"edit_member\",\n });\n};\n\nconst handleCancelModerators = () => {\n setEditModerators(false);\n setModerators(accessControlInfo.members_list[\"team:moderators\"].children);\n};\n\nreturn (\n <>\n <h1>Moderators</h1>\n <div className=\"card-body\">\n <h5>\n The moderator group has permissions to edit any posts and apply all\n labels, including restricted ones.\n </h5>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.PostControls\"}\n props={{\n icon: \"bi bi-gear-wide-connected\",\n className: \"mb-3\",\n\n title: \"Edit members\",\n onClick: () => setEditModerators(!editModerators),\n testId: \"edit-members\",\n }}\n />\n </div>\n <Tile className=\"p-3\">\n {editModerators ? (\n <>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.ListEditor\"\n props={{\n data: {\n maxLength: 100,\n placeholder: \"member.near\",\n prefix: \"member\",\n list: moderators,\n },\n setList: setModerators,\n // Could add a check to see if it is an valid account id.\n validate: (newItem) => true,\n invalidate: () => null,\n }}\n />\n <div\n className={\n \"d-flex align-items-center justify-content-end gap-3 mt-4\"\n }\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: {\n root: \"btn-outline-danger shadow-none border-0\",\n },\n label: \"Cancel\",\n onClick: handleCancelModerators,\n }}\n />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn\" },\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Submit\",\n onClick: handleEditModerators,\n }}\n />\n </div>\n </>\n ) : (\n <>\n <div class=\"pt-4\">Members</div>\n\n {moderators && (\n <div class=\"vstack\">\n {moderators.length ? (\n moderators.map((child) => (\n <Tile className=\"w-25 p-3 m-1\" minHeight={10}>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.ProfileLine`}\n props={{ accountId: child }}\n />\n </Tile>\n ))\n ) : (\n <div>No moderators</div>\n )}\n </div>\n )}\n </>\n )}\n </Tile>\n </>\n);\n" }, "devhub.entity.community.configuration.AddonsConfigurator": { "": "const { getAllAddons } =\n VM.require(\"devhub.megha19.near/widget/core.adapter.devhub-contract\") ||\n (() => {});\n\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nconst availableAddons = getAllAddons() || [];\n\nconst isActive = props.isActive;\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n`;\n\nconst Item = styled.div`\n padding: 10px;\n margin: 5px;\n display: flex;\n align-items: center;\n gap: 10px;\n`;\n\nconst Icon = styled.span`\n margin-right: 10px;\n`;\n\nconst EditableField = styled.input`\n flex: 1;\n`;\n\nconst ToggleButton = styled.input`\n margin-left: 10px;\n`;\n\nconst Table = styled.table`\n width: 100%;\n border-collapse: collapse;\n`;\n\nconst Header = styled.thead`\n background-color: #f0f0f0;\n`;\n\nconst HeaderCell = styled.th`\n padding: 10px;\n text-align: left;\n`;\n\nconst Row = styled.tr``;\n\nconst Cell = styled.td`\n padding: 10px;\n`;\n\nfunction generateRandom6CharUUID() {\n const chars = \"0123456789abcdefghijklmnopqrstuvwxyz\";\n let result = \"\";\n\n for (let i = 0; i < 6; i++) {\n const randomIndex = Math.floor(Math.random() * chars.length);\n result += chars[randomIndex];\n }\n\n return result;\n}\n\nconst AddonItem = ({\n data,\n onUpdate,\n onMove,\n onRemove,\n index,\n isTop,\n isBottom,\n}) => {\n const handleNameChange = (event) => {\n const newName = event.target.value;\n onUpdate({ ...data, display_name: newName });\n };\n\n const handleEnableChange = () => {\n onUpdate({ ...data, enabled: !data.enabled });\n };\n\n const moveItemUp = () => {\n if (!isTop) {\n onMove(index, index - 1);\n }\n };\n\n const moveItemDown = () => {\n if (!isBottom) {\n onMove(index, index + 1);\n }\n };\n\n const removeItem = () => {\n onRemove(data.id);\n };\n\n const addonMatch =\n availableAddons.find((it) => it.id === data.addon_id) ?? null;\n\n return (\n <Row>\n <Cell>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0\" }}>\n <button\n className=\"btn btn-sm btn-secondary rounded-0\"\n onClick={moveItemUp}\n disabled={!isActive || isTop}\n style={{ visibility: isTop && !isBottom ? \"hidden\" : \"visible\" }}\n >\n <i className=\"bi bi-arrow-up\"></i>\n </button>\n <button\n className=\"btn btn-sm btn-secondary rounded-0\"\n onClick={moveItemDown}\n disabled={!isActive || isBottom}\n style={{ visibility: isBottom && !isTop ? \"hidden\" : \"visible\" }}\n >\n <i className=\"bi bi-arrow-down\"></i>\n </button>\n </div>\n </Cell>\n <Cell>\n <div>{addonMatch.title}</div>\n </Cell>\n <Cell>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n label: \" \",\n value: data.display_name,\n onChange: handleNameChange,\n inputProps: {\n min: 3,\n max: 30,\n disabled: !data.enabled || !isActive,\n },\n }}\n />\n </Cell>\n <Cell>\n <div\n className={\n \"d-flex flex-column flex-1 align-items-start justify-content-evenly\"\n }\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.atom.Toggle\"}\n props={{\n value: data.enabled,\n onChange: handleEnableChange,\n disabled: !isActive,\n }}\n />\n </div>\n </Cell>\n <Cell>\n <div style={{ display: \"flex\", gap: \"2px\" }}>\n {isActive && (\n <button className=\"btn btn-outline-danger\" onClick={removeItem}>\n <i className=\"bi bi-trash-fill\" />\n </button>\n )}\n </div>\n </Cell>\n </Row>\n );\n};\n\nfunction arraysAreEqual(arr1, arr2) {\n if (arr1.length !== arr2.length) {\n return false;\n }\n for (let i = 0; i < arr1.length; i++) {\n if (arr1[i] !== arr2[i]) {\n return false;\n }\n }\n return true;\n}\n\nconst AddonsConfigurator = ({ data, onSubmit }) => {\n const [originalList, setOriginalList] = useState(data);\n const [list, setList] = useState(data);\n const [changesMade, setChangesMade] = useState(false);\n\n useEffect(() => {\n setOriginalList(data);\n }, [data]);\n\n const updateItem = (updatedItem) => {\n const updatedList = list.map((item) =>\n item.id === updatedItem.id ? updatedItem : item\n );\n setList(updatedList);\n setChangesMade(!arraysAreEqual(originalList, updatedList));\n };\n\n const moveItem = (fromIndex, toIndex) => {\n const updatedList = [...list];\n const [movedItem] = updatedList.splice(fromIndex, 1);\n updatedList.splice(toIndex, 0, movedItem);\n setList(updatedList);\n setChangesMade(!arraysAreEqual(originalList, updatedList));\n };\n\n const [selectedAddon, setSelectedAddon] = useState(null);\n\n const handleAddItem = () => {\n const newItem = {\n id: generateRandom6CharUUID(),\n addon_id: selectedAddon.id,\n display_name: selectedAddon.title,\n enabled: true,\n parameters: \"{}\",\n };\n const updatedList = [...list, newItem];\n setList(updatedList);\n setChangesMade(!arraysAreEqual(originalList, updatedList));\n setSelectedAddon(null);\n };\n\n const removeItem = (id) => {\n const updatedList = list.filter((item) => item.id !== id);\n setList(updatedList);\n setChangesMade(!arraysAreEqual(originalList, updatedList));\n };\n\n return (\n <Container>\n <p>\n Add or remove custom tabs, which will appear in your community's\n navigation bar.\n <br />\n You can customize them on each page.\n </p>\n {list.length > 0 && (\n <Table>\n <Header>\n <Row>\n <HeaderCell style={{ width: \"30px\" }}>Order</HeaderCell>\n <HeaderCell>Tab Type</HeaderCell>\n <HeaderCell>Tab Name</HeaderCell>\n <HeaderCell style={{ width: \"45px\" }}>Enabled</HeaderCell>\n {isActive && (\n <HeaderCell style={{ width: \"40px\" }}>Actions</HeaderCell>\n )}\n </Row>\n </Header>\n <tbody data-testid=\"addon-table\">\n {list.map((item, index) => (\n <AddonItem\n key={item.id}\n data={item}\n onUpdate={updateItem}\n onMove={moveItem}\n onRemove={removeItem}\n index={index}\n isTop={index === 0}\n isBottom={index === list.length - 1}\n />\n ))}\n </tbody>\n </Table>\n )}\n {isActive && availableAddons && (\n <div className=\"d-flex justify-content-center pt-2\">\n <div className=\"d-flex gap-2 flex-grow-1 px-4\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Select\"}\n props={{\n className: \"flex-grow-1\",\n options: availableAddons.map((addon) => ({\n label: addon.title,\n value: addon.id,\n })),\n value: selectedAddon.id ?? \"\",\n onChange: (e) =>\n setSelectedAddon(\n availableAddons.find((addon) => addon.id === e.target.value)\n ),\n placeholder: \"Select an addon\",\n }}\n />\n <button\n className=\"btn btn-success\"\n onClick={handleAddItem}\n disabled={!selectedAddon}\n >\n <i className=\"bi bi-plus\" />\n </button>\n </div>\n </div>\n )}\n {isActive && (\n <div\n className={\"d-flex align-items-center justify-content-end gap-3 mt-4\"}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-success\" },\n disabled: !changesMade,\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Submit\",\n onClick: () => onSubmit(list),\n }}\n />\n </div>\n )}\n </Container>\n );\n};\n\nreturn AddonsConfigurator(props);\n" }, "devhub.entity.community.Card": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nif (!href) {\n return <></>;\n}\n\nconst Card = styled.div`\n cursor: pointer;\n background-color: white;\n border-radius: 0.5rem;\n padding: 1.5rem;\n gap: 1rem;\n height: 100%;\n min-height: 12rem;\n\n display: flex;\n align-items: center;\n justify-content: flex-start;\n transition: all 300ms;\n box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);\n\n &:hover {\n box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);\n }\n\n img.logo {\n height: 6rem;\n width: 6rem;\n border-radius: 50%;\n\n object-fit: cover;\n }\n\n h3,\n p {\n margin: 0;\n }\n\n h3 {\n font-size: 1.25rem;\n font-weight: 600;\n }\n\n p {\n font-size: 1rem;\n font-weight: 400;\n }\n`;\n\nconst CommunityCard = ({ metadata }) => {\n const { handle, logo_url, name, description } = metadata;\n const link = href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"community\", handle: handle },\n });\n\n return (\n <Link to={link} style={{ all: \"unset\" }}>\n <Card>\n <img className=\"logo\" src={logo_url} />\n\n <div className=\"d-flex flex-column justify-content-center gap-1 w-100\">\n <h3>{name}</h3>\n <p>{description}</p>\n </div>\n </Card>\n </Link>\n );\n};\n\nreturn CommunityCard(props);\n" }, "devhub.entity.post.PostEditor": { "": "const { normalize } = VM.require(\"devhub.megha19.near/widget/core.lib.stringUtils\");\nconst { getDepositAmountForWriteAccess } = VM.require(\n \"devhub.megha19.near/widget/core.lib.common\"\n);\n\ngetDepositAmountForWriteAccess || (getDepositAmountForWriteAccess = () => {});\nnormalize || (normalize = () => {});\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: 384px;\n`;\n\nconst LoadingButtonSpinner = (\n <span\n class=\"submit-post-loading-indicator spinner-border spinner-border-sm\"\n role=\"status\"\n aria-hidden=\"true\"\n ></span>\n);\n\nfunction initLabels() {\n const labels = [];\n if (typeof props.labels === \"string\") {\n labels.push(...props.labels.split(\",\"));\n }\n if (Array.isArray(props.labels)) {\n labels.push(...props.labels);\n }\n if (props.referral) {\n labels.push(`referral:${props.referral}`);\n }\n return labels;\n}\n\nif (!context.accountId) {\n return (\n <CenteredMessage height={\"384px\"}>\n <h2>Please sign in to create a post.</h2>\n </CenteredMessage>\n );\n}\n\nconst userStorageDeposit = Near.view(\n \"social.near\",\n \"storage_balance_of\",\n {\n account_id: context.accountId,\n }\n);\n\nconst cleanDescription = (description) => {\n return description\n ? description.replace(\n /###### Requested amount: .+?\\n###### Requested sponsor: @[^\\s]+\\n/g,\n \"\"\n )\n : description;\n};\n\nconst postTypeOptions = {\n Idea: {\n name: \"Idea\",\n icon: \"bi-lightbulb\",\n description:\n \"Get feedback from the community about a problem, opportunity, or need.\",\n },\n\n Solution: {\n name: \"Solution\",\n icon: \"bi-rocket\",\n description:\n \"Provide a specific proposal or implementation to an idea, optionally requesting funding. If your solution relates to an existing idea, please reply to the original post with a solution.\",\n },\n};\n\nlet fields = {\n Comment: [\"description\"],\n Idea: [\"name\", \"description\"],\n Solution: [\"name\", \"description\", \"fund_raising\"],\n Attestation: [\"name\", \"description\"],\n Sponsorship: [\n \"name\",\n \"description\",\n \"amount\",\n \"sponsorship_token\",\n \"supervisor\",\n ],\n Github: [\"githubLink\", \"name\", \"description\"],\n};\n\nconst isCreatePostPage = props.isCreatePostPage ?? false;\nconst postType = props.postType ?? \"Idea\";\nconst parentId = props.parentId ?? null;\nconst mode = props.mode ?? \"Create\";\nconst labelStrings = initLabels();\nconst [postIdList, setPostIdList] = useState(null); // to show updated post after approve txn\nconst [showPostPage, setShowPostPage] = useState(false); // show newly created post\nconst [postId, setPostId] = useState(props.postId ?? null);\nconst [postData, setPostData] = useState(null); // for capturing edit post change\nconst [isTxnCreated, setCreateTxn] = useState(false);\nconst [isSubmittingTransaction, setIsSubmittingTransaction] = useState(false);\n\nuseEffect(() => {\n if (mode == \"Edit\") {\n const data = Near.view(\"devgovgigs.near\", \"get_post\", {\n post_id: postId,\n });\n if (!postData) {\n setPostData(data);\n }\n if (postData && data && JSON.stringify(postData) !== JSON.stringify(data)) {\n props.setEditorState(false);\n props.setExpandReplies(true);\n setPostData(data);\n }\n } else {\n const postIds = Near.view(\"devgovgigs.near\", \"get_all_post_ids\");\n if (!postIdList) {\n setPostIdList(postIds);\n }\n if (\n isTxnCreated &&\n postIdList?.length > 0 &&\n postIds.length > 0 &&\n postIdList.length !== postIds.length\n ) {\n props.onDraftStateChange(null);\n if (isCreatePostPage) {\n setShowPostPage(true);\n setPostId(postIds[postIds?.length - 1]);\n } else {\n // close editor and expand replies\n props.setEditorState(false);\n props.setExpandReplies(true);\n }\n setPostIdList(postIds);\n }\n }\n});\n\nconst labels = labelStrings.map((s) => {\n return { name: s };\n});\n\ninitState({\n seekingFunding: props.seekingFunding ?? false,\n author_id: context.accountId,\n // Should be a list of objects with field \"name\".\n labels,\n // Should be a list of labels as strings.\n // Both of the label structures should be modified together.\n labelStrings: [],\n postType,\n name: props.name ?? \"\",\n description:\n (props.postType === \"Solution\"\n ? cleanDescription(props.description)\n : props.description) ?? \"\",\n amount: props.amount ?? \"0\",\n token: props.token ?? \"USDT\",\n supervisor: props.supervisor ?? \"neardevdao.near\",\n githubLink: props.githubLink ?? \"\",\n warning: \"\",\n draftStateApplied: false,\n mentionInput: \"\", // text next to @ tag\n mentionsArray: [], // all the mentions in the description\n displayFields: fields[postType],\n});\n\n/* INCLUDE: \"core/lib/autocomplete\" */\nconst autocompleteEnabled = true;\n\nconst AutoComplete = styled.div`\n z-index: 5;\n\n > div > div {\n padding: calc(var(--padding) / 2);\n }\n`;\n\nif (props.transactionHashes) {\n const transaction = useCache(\n () =>\n asyncFetch(\"https://rpc.mainnet.near.org\", {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: \"dontcare\",\n method: \"tx\",\n params: [props.transactionHashes, context.accountId],\n }),\n }).then((res) => res),\n props.transactionHashes + context.accountId,\n { subscribe: false }\n );\n\n if (transaction !== null) {\n const transaction_method_name =\n transaction?.body?.result?.transaction?.actions[0].FunctionCall\n .method_name;\n\n const is_edit_or_add_post_transaction =\n transaction_method_name == \"add_post\" ||\n transaction_method_name == \"edit_post\";\n\n if (is_edit_or_add_post_transaction) {\n props.onDraftStateChange(null);\n }\n\n // show the latest created post to user\n if (transaction_method_name == \"add_post\" && isCreatePostPage) {\n setShowPostPage(true);\n setPostId(postIdList?.[postIdList?.length - 1]);\n }\n }\n}\n\nfunction textareaInputHandler(value) {\n const words = value.split(/\\s+/);\n const allMentiones = words\n .filter((word) => word.startsWith(\"@\"))\n .map((mention) => mention.slice(1));\n const newMentiones = allMentiones.filter(\n (item) => !state.mentionsArray.includes(item)\n );\n\n State.update((lastKnownState) => ({\n ...lastKnownState,\n text: value,\n showAccountAutocomplete: newMentiones?.length > 0,\n mentionsArray: allMentiones,\n mentionInput: newMentiones?.[0] ?? \"\",\n }));\n}\n\nfunction autoCompleteAccountId(id) {\n // to make sure we update the @ at correct index\n let currentIndex = 0;\n const updatedDescription = state.description.replace(\n /(?:^|\\s)(@[^\\s]*)/g,\n (match) => {\n if (currentIndex === state.mentionsArray.indexOf(state.mentionInput)) {\n currentIndex++;\n return ` @${id}`;\n } else {\n currentIndex++;\n return match;\n }\n }\n );\n State.update((lastKnownState) => ({\n ...lastKnownState,\n handler: \"autocompleteSelected\",\n description: updatedDescription,\n showAccountAutocomplete: false,\n }));\n}\n\n/* END_INCLUDE: \"core/lib/autocomplete\" */\n\nif (!state.draftStateApplied && props.draftState) {\n State.update({ ...props.draftState, draftStateApplied: true });\n}\n\nconst typeSwitch = (optionName) => {\n State.update({\n postType: optionName,\n displayFields: fields[optionName],\n });\n};\n\n// This must be outside onClick, because Near.view returns null at first, and when the view call finished, it returns true/false.\n// If checking this inside onClick, 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);\nif (grantNotify === null || userStorageDeposit === null) {\n return;\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};\n\nconst onSubmit = () => {\n setIsSubmittingTransaction(true);\n let labels = state.labelStrings;\n var body = {\n Comment: { description: state.description, comment_version: \"V2\" },\n Idea: {\n name: state.name,\n description: state.description,\n idea_version: \"V1\",\n },\n Solution: {\n name: state.name,\n description: generateDescription(\n state.description,\n state.amount,\n state.token,\n state.supervisor,\n state.seekingFunding\n ),\n solution_version: \"V1\",\n },\n Attestation: {\n name: state.name,\n description: state.description,\n attestation_version: \"V1\",\n },\n Sponsorship: {\n name: state.name,\n description: state.description,\n amount: state.amount,\n sponsorship_token: tokenMapping[state.token],\n supervisor: state.supervisor,\n sponsorship_version: \"V1\",\n },\n Github: {\n name: state.name,\n description: state.description,\n github_version: \"V0\",\n github_link: state.githubLink,\n },\n }[state.postType];\n body[\"post_type\"] = state.postType;\n if (!context.accountId) {\n return;\n }\n let txn = [];\n if (mode == \"Create\") {\n props.onDraftStateChange(\n Object.assign({}, state, { parent_post_id: parentId })\n );\n txn.push({\n contractName: \"devgovgigs.near\",\n methodName: \"add_post\",\n args: {\n parent_id: parentId,\n labels,\n body,\n },\n gas: Big(10).pow(14),\n });\n } else if (mode == \"Edit\") {\n props.onDraftStateChange(\n Object.assign({}, state, { edit_post_id: postId })\n );\n txn.push({\n contractName: \"devgovgigs.near\",\n methodName: \"edit_post\",\n args: {\n id: postId,\n labels,\n body,\n },\n gas: Big(10).pow(14),\n });\n }\n if (mode == \"Create\" || mode == \"Edit\") {\n if (grantNotify === false) {\n txn.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 setCreateTxn(true);\n Near.call(txn);\n }\n};\n\nconst checkLabel = (label) => {\n Near.asyncView(\"devgovgigs.near\", \"is_allowed_to_use_labels\", {\n editor: context.accountId,\n labels: [label],\n }).then((allowed) => {\n if (allowed) {\n State.update({ warning: \"\" });\n } else {\n State.update({\n warning:\n 'The label \"' +\n label +\n '\" is protected and can only be added by moderators',\n });\n return;\n }\n });\n};\n\nconst setLabels = (labels) => {\n labels = labels.map((o) => {\n o.name = normalize(o.name);\n return o;\n });\n if (labels.length < state.labels.length) {\n let oldLabels = new Set(state.labels.map((label) => label.name));\n for (let label of labels) {\n oldLabels.delete(label.name);\n }\n let removed = oldLabels.values().next().value;\n Near.asyncView(\"devgovgigs.near\", \"is_allowed_to_use_labels\", {\n editor: context.accountId,\n labels: [removed],\n }).then((allowed) => {\n if (allowed) {\n let labelStrings = labels.map(({ name }) => name);\n State.update({ labels, labelStrings });\n } else {\n State.update({\n warning:\n 'The label \"' +\n removed +\n '\" is protected and can only be updated by moderators',\n });\n return;\n }\n });\n } else {\n let labelStrings = labels.map((o) => {\n return o.name;\n });\n State.update({ labels, labelStrings });\n }\n};\nconst existingLabelStrings =\n Near.view(\"devgovgigs.near\", \"get_all_allowed_labels\", {\n editor: context.accountId,\n }) ?? [];\nconst existingLabelSet = new Set(existingLabelStrings);\nconst existingLabels = existingLabelStrings\n .filter((it) => it !== \"blog\") // remove blog label so users cannot publish blogs from feed\n .map((s) => {\n return { name: s };\n });\n\nconst labelEditor = (\n <div className=\"col-lg-12 mb-2\">\n <label htmlFor=\"labels\" className=\"fs-6 fw-bold mb-1\">\n Labels\n </label>\n <Typeahead\n multiple\n labelKey=\"name\"\n onInputChange={checkLabel}\n onChange={setLabels}\n options={existingLabels}\n placeholder=\"near.social, widget, NEP, standard, protocol, tool\"\n selected={state.labels}\n positionFixed\n allowNew={(results, props) => {\n return (\n !existingLabelSet.has(props.text) &&\n props.text.toLowerCase() !== \"blog\" && // dont allow adding \"Blog\"\n props.selected.filter((selected) => selected.name === props.text)\n .length == 0 &&\n Near.view(\"devgovgigs.near\", \"is_allowed_to_use_labels\", {\n editor: context.accountId,\n labels: [props.text],\n })\n );\n }}\n />\n </div>\n);\n\nconst githubLinkDiv = (\n <div className=\"col-lg-12 mb-2\">\n Github Issue URL:\n <input\n type=\"text\"\n value={state.githubLink}\n onChange={(event) => State.update({ githubLink: event.target.value })}\n />\n </div>\n);\n\nconst nameDiv = (\n <div className=\"col-lg-12 mb-2\">\n <label htmlFor=\"title\" className=\"fs-6 fw-bold mb-1\">\n Title\n </label>\n <input\n name=\"title\"\n id=\"title\"\n data-testid=\"name-editor\"\n type=\"text\"\n value={state.name}\n onChange={(event) => State.update({ name: event.target.value })}\n />\n </div>\n);\n\nconst amountDiv = (\n <div className=\"col-lg-6 mb-2\">\n Amount:\n <input\n type=\"text\"\n value={state.amount}\n onChange={(event) => State.update({ amount: event.target.value })}\n />\n </div>\n);\n\nconst tokenDiv = (\n <div className=\"col-lg-6 mb-2\">\n Currency\n <select\n onChange={(event) => State.update({ token: event.target.value })}\n class=\"form-select\"\n aria-label=\"Select currency\"\n value={state.token}\n >\n <option value=\"USDT\">USDT</option>\n <option value=\"NEAR\">NEAR</option>\n <option value=\"USDC\">USDC</option>\n </select>\n </div>\n);\n\nconst supervisorDiv = (\n <div className=\"col-lg-6 mb-2\">\n Supervisor:\n <input\n type=\"text\"\n value={state.supervisor}\n onChange={(event) => State.update({ supervisor: event.target.value })}\n />\n </div>\n);\n\nconst callDescriptionDiv = () => {\n return (\n <div className=\"col-lg-12 mb-2\">\n <label htmlFor=\"description\" className=\"fs-6 fw-bold mb-1\">\n Description\n </label>\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.MarkdownEditor\"}\n props={{\n data: { handler: state.handler, content: state.description },\n onChange: (content) => {\n State.update({ description: content, handler: \"update\" });\n textareaInputHandler(content);\n },\n }}\n />\n {autocompleteEnabled && state.showAccountAutocomplete && (\n <AutoComplete>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.AccountAutocomplete\"\n props={{\n term: state.mentionInput,\n onSelect: autoCompleteAccountId,\n onClose: () => State.update({ showAccountAutocomplete: false }),\n }}\n />\n </AutoComplete>\n )}\n </div>\n );\n};\n\nconst disclaimer = (\n <p>\n <i>\n * Note, all projects that were granted sponsorships are required to pass\n KYC to receive the funding.\n </i>\n </p>\n);\n\nconst isFundraisingDiv = (\n // This is jank with just btns and not radios. But the radios were glitchy af\n <>\n <div class=\"mb-2\">\n <p class=\"fs-6 fw-bold mb-1\">\n Are you seeking funding for your solution?\n <span class=\"text-muted fw-normal\">(Optional)</span>\n </p>\n <div class=\"form-check form-check-inline\">\n <label class=\"form-check-label\">\n <button\n className=\"btn btn-light p-0\"\n style={{\n backgroundColor: state.seekingFunding ? \"#0C7283\" : \"inherit\",\n color: \"#f3f3f3\",\n border: \"solid #D9D9D9\",\n borderRadius: \"100%\",\n height: \"20px\",\n width: \"20px\",\n }}\n onClick={() => State.update({ seekingFunding: true })}\n />\n Yes\n </label>\n </div>\n <div class=\"form-check form-check-inline\">\n <label class=\"form-check-label\">\n <button\n className=\"btn btn-light p-0\"\n style={{\n backgroundColor: !state.seekingFunding ? \"#0C7283\" : \"inherit\",\n color: \"#f3f3f3\",\n border: \"solid #D9D9D9\",\n borderRadius: \"100%\",\n height: \"20px\",\n width: \"20px\",\n }}\n onClick={() => State.update({ seekingFunding: false })}\n />\n No\n </label>\n </div>\n </div>\n </>\n);\n\nconst fundraisingDiv = (\n <div class=\"d-flex flex-column mb-2\">\n <div className=\"col-lg-6 mb-2\">\n Currency\n <select\n onChange={(event) => State.update({ token: event.target.value })}\n class=\"form-select\"\n aria-label=\"Default select example\"\n value={state.token}\n >\n <option value=\"USDT\">USDT</option>\n <option value=\"NEAR\">NEAR</option>\n <option value=\"USDC\">USDC</option>\n </select>\n </div>\n <div className=\"col-lg-6 mb-2\">\n Requested amount\n <span class=\"text-muted fw-normal\">(Numbers Only)</span>\n <input\n data-testid=\"requested-amount-editor\"\n type=\"number\"\n value={parseInt(state.amount) > 0 ? state.amount : \"\"}\n min={0}\n onChange={(event) => {\n State.update({\n amount: Number(\n event.target.value.toString().replace(/e/g, \"\")\n ).toString(),\n });\n }}\n />\n </div>\n <div className=\"col-lg-6 mb-2\">\n <p class=\"mb-1\">\n Requested sponsor\n <span class=\"text-muted fw-normal\">(Optional)</span>\n </p>\n <p style={{ fontSize: \"13px\" }} class=\"m-0 text-muted fw-light\">\n If you are requesting funding from a specific sponsor, please enter\n their username.\n </p>\n <div class=\"input-group flex-nowrap\">\n <span class=\"input-group-text\" id=\"addon-wrapping\">\n @\n </span>\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"Enter username\"\n value={state.supervisor}\n onChange={(event) => State.update({ supervisor: event.target.value })}\n />\n </div>\n </div>\n </div>\n);\n\nfunction generateDescription(text, amount, token, supervisor, seekingFunding) {\n const fundingText =\n amount > 0 && token ? `###### Requested amount: ${amount} ${token}\\n` : \"\";\n const supervisorText = supervisor\n ? `###### Requested sponsor: @${supervisor}\\n`\n : \"\";\n return seekingFunding ? `${fundingText}${supervisorText}${text}` : text;\n}\n\nconst [tab, setTab] = useState(\"editor\");\n\nconst renamedPostType =\n state.postType == \"Submission\" ? \"Solution\" : state.postType;\n// Below there is a weird code with fields.includes(\"githubLink\") ternary operator.\n// This is to hack around rendering bug of near.social.\n\nreturn (\n <div className=\"d-flex flex-column flex-grow-1 w-100\">\n <div className=\"mx-2 mx-md-5 mb-5\">\n {showPostPage ? (\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.post.Post\"}\n props={{\n id: postId,\n expandable: true,\n defaultExpanded: false,\n isInList: true,\n isPreview: false,\n onDraftStateChange: props.onDraftStateChange,\n referral: postId,\n transactionHashes: props.transactionHashes,\n }}\n />\n ) : (\n <div className=\"card\">\n <div className=\"card-header\">\n <div>\n <ul class=\"nav nav-tabs\">\n <li class=\"nav-item\">\n <button\n class={`nav-link ${tab === \"editor\" ? \"active\" : \"\"}`}\n onClick={() => setTab(\"editor\")}\n >\n Editor\n </button>\n </li>\n <li class=\"nav-item\">\n <button\n class={`nav-link ${tab === \"preview\" ? \"active\" : \"\"}`}\n onClick={() => setTab(\"preview\")}\n >\n Preview\n </button>\n </li>\n </ul>\n </div>\n {!isCreatePostPage && tab === \"editor\" && (\n <div className=\"my-3\">\n {mode} {renamedPostType}\n </div>\n )}\n {tab === \"preview\" && <div className=\"my-3\">Post Preview</div>}\n </div>\n <div class=\"card-body\">\n {tab === \"editor\" && (\n <>\n {state.warning && (\n <div\n class=\"alert alert-warning alert-dismissible fade show\"\n role=\"alert\"\n >\n {state.warning}\n <button\n type=\"button\"\n class=\"btn-close\"\n data-bs-dismiss=\"alert\"\n aria-label=\"Close\"\n onClick={() => State.update({ warning: \"\" })}\n ></button>\n </div>\n )}\n {isCreatePostPage && (\n <div>\n <p class=\"card-title fw-bold fs-6\">\n What do you want to create?\n </p>\n <div class=\"d-flex flex-row gap-2\">\n {Object.values(postTypeOptions).map((option) => (\n <button\n className={`btn btn-${\n state.postType === option.name\n ? \"primary\"\n : \"outline-secondary\"\n }`}\n data-testid={`btn-${option.name.toLowerCase()}`}\n key={option.name}\n onClick={() => typeSwitch(option.name)}\n style={\n state.postType === option.name\n ? {\n backgroundColor: \"#0C7283\",\n color: \"#f3f3f3\",\n }\n : null\n }\n type=\"button\"\n >\n <i className={`bi ${option.icon}`} />\n <span>{option.name}</span>\n </button>\n ))}\n </div>\n <p class=\"text-muted w-75 my-1\">\n {postTypeOptions[state.postType].description}\n </p>\n </div>\n )}\n {/* This statement around the githubLinkDiv creates a weird render bug\n where the title renders extra on state change. */}\n {state.displayFields.includes(\"githubLink\") ? (\n <div className=\"row\">\n {state.displayFields.includes(\"githubLink\") &&\n githubLinkDiv}\n {labelEditor}\n {state.displayFields.includes(\"name\") && nameDiv}\n {state.displayFields.includes(\"description\") &&\n callDescriptionDiv()}\n </div>\n ) : (\n <div className=\"row\">\n {labelEditor}\n {state.displayFields.includes(\"name\") && nameDiv}\n {state.displayFields.includes(\"amount\") && amountDiv}\n {state.displayFields.includes(\"sponsorship_token\") &&\n tokenDiv}\n {state.displayFields.includes(\"supervisor\") &&\n supervisorDiv}\n {state.displayFields.includes(\"description\") &&\n callDescriptionDiv()}\n {state.displayFields.includes(\"fund_raising\") &&\n isFundraisingDiv}\n {state.seekingFunding &&\n state.displayFields.includes(\"fund_raising\") &&\n fundraisingDiv}\n </div>\n )}\n\n {disclaimer}\n </>\n )}\n {tab === \"preview\" && (\n <div className=\"mb-2\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.post.Post\"\n props={{\n isPreview: true,\n id: 0, // irrelevant\n post: {\n author_id: state.author_id,\n likes: [],\n snapshot: {\n editor_id: state.editor_id,\n labels: state.labelStrings,\n post_type: postType,\n name: state.name,\n description:\n state.postType == \"Solution\"\n ? generateDescription(\n state.description,\n state.amount,\n state.token,\n state.supervisor,\n state.seekingFunding\n )\n : state.description,\n amount: state.amount,\n sponsorship_token: state.token,\n supervisor: state.supervisor,\n github_link: state.githubLink,\n },\n },\n }}\n />\n </div>\n )}\n\n <>\n <button\n data-testid=\"submit-create-post\"\n style={{\n width: \"7rem\",\n backgroundColor: \"#0C7283\",\n color: \"#f3f3f3\",\n }}\n disabled={\n (state.seekingFunding &&\n (!state.amount || state.amount < 1)) ||\n (isCreatePostPage &&\n (state.name === \"\" || state.description === \"\"))\n }\n className=\"btn btn-light mb-2 p-3\"\n onClick={onSubmit}\n disabled={isSubmittingTransaction}\n >\n {isSubmittingTransaction ? LoadingButtonSpinner : <></>}\n Submit\n </button>\n {!isCreatePostPage && (\n <button\n style={{\n width: \"7rem\",\n backgroundColor: \"#fff\",\n color: \"#000\",\n }}\n className=\"btn btn-light mb-2 p-3\"\n onClick={() => props.setEditorState(false)}\n >\n Cancel\n </button>\n )}\n </>\n </div>\n </div>\n )}\n </div>\n </div>\n);\n" }, "devhub.entity.proposal.Feed": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nif (!href) {\n return <p>Loading modules...</p>;\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 .green-btn {\n background-color: #04a46e !important;\n border: none;\n color: white;\n\n &:active {\n color: white;\n }\n }\n\n @media screen and (max-width: 768px) {\n .green-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\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: `devhub.near/post/main`,\n blockHeight: blockHeight,\n };\n\n return (\n <a\n href={href({\n widgetSrc: \"devhub.megha19.near/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.megha19.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 <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.proposal.CategoryTag\"}\n props={{\n category: proposal.category,\n }}\n />\n </div>\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.megha19.near/widget/devhub.entity.proposal.LikeButton\"\n props={{\n item,\n proposalId: proposal.id,\n notifyAccountId: accountId,\n }}\n />\n\n <Widget\n src={\"devhub.megha19.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.megha19.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(\"devhub.near\", \"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 cachedItems: {},\n author: \"\",\n stage: \"\",\n sort: \"\",\n category: \"\",\n input: \"\",\n loading: false,\n loadingMore: false,\n aggregatedCount: 0,\n currentlyDisplaying: 0,\n });\n\n const queryName =\n \"thomasguntenaar_near_devhub_proposals_quebec_proposals_with_latest_snapshot\";\n const 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 author_id\n block_height\n name\n category\n summary\n editor_id\n proposal_id\n ts\n timeline\n views\n }\n ${queryName}_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\": `thomasguntenaar_near` },\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 where = { category: { _eq: state.category }, ...where };\n }\n\n if (state.stage) {\n // timeline is stored as jsonb\n where = {\n timeline: { _cast: { String: { _ilike: `%${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 = { description: { _ilike: `%${text}%` }, ...where };\n }\n }\n\n return where;\n };\n\n const buildOrderByClause = () => {\n /**\n * TODO\n * Most commented -> edit contract and indexer\n * Unanswered -> 0 comments\n */\n };\n\n const makeMoreItems = () => {\n if (state.aggregatedCount <= state.currentlyDisplaying) return;\n fetchProposals(state.data.length);\n };\n\n const fetchProposals = (offset) => {\n if (!offset) {\n offset = 0;\n }\n if (state.loading) return;\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 =\n result.body.data\n .thomasguntenaar_near_devhub_proposals_quebec_proposals_with_latest_snapshot;\n const totalResult =\n result.body.data\n .thomasguntenaar_near_devhub_proposals_quebec_proposals_with_latest_snapshot_aggregate;\n State.update({ aggregatedCount: totalResult.aggregate.count });\n // Parse timeline\n fetchBlockHeights(data, offset);\n }\n }\n });\n };\n\n const renderItem = (item, index) => (\n <div\n key={item.proposal_id}\n className={\n (index !== state.data.length - 1 && \"border-bottom \") + index === 0 &&\n \" rounded-top-2\"\n }\n >\n <FeedItem proposal={item} index={index} />\n </div>\n );\n const cachedRenderItem = (item, index) => {\n if (props.term) {\n return renderItem(item, {\n searchKeywords: [props.term],\n });\n }\n\n const key = JSON.stringify(item);\n\n if (!(key in state.cachedItems)) {\n state.cachedItems[key] = renderItem(item, index);\n State.update();\n }\n return state.cachedItems[key];\n };\n\n useEffect(() => {\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 });\n } else {\n State.update({\n data,\n currentlyDisplaying: data.length,\n loading: false,\n });\n }\n });\n };\n\n const loader = (\n <div className=\"d-flex justify-content-center align-items-center w-100\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Spinner\"}\n />\n </div>\n );\n\n const renderedItems = state.data ? state.data.map(cachedRenderItem) : null;\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 DevDAO 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.megha19.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 fetchProposals();\n },\n onEnter: () => {\n fetchProposals();\n },\n }}\n />\n <Widget\n src={\"devhub.megha19.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 <Widget\n src={\n \"devhub.megha19.near/widget/devhub.feature.proposal-search.by-category\"\n }\n props={{\n onStateChange: (select) => {\n State.update({ category: select.value });\n },\n }}\n />\n <Widget\n src={\n \"devhub.megha19.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.megha19.near/widget/devhub.feature.proposal-search.by-author\"\n }\n props={{\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: \"devhub.megha19.near/widget/app\",\n params: { page: \"create-proposal\" },\n })}\n >\n <Widget\n src={\"devhub.megha19.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 New Proposal\n </div>\n ),\n classNames: { root: \"green-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 <div className=\"text-muted bg-grey text-sm mt-2 p-3 rounded-3\">\n <p className=\"d-flex gap-3 align-items-center mb-0\">\n <div>\n <i class=\"bi bi-info-circle\"></i>\n </div>\n <div>\n <span className=\"fw-bold\">\n Welcome to\n <a\n href=\"https://near.social/devhub.near/widget/app?page=community&handle=developer-dao&tab=overview\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n DevDAO’s New Proposal Feed!\n </a>\n </span>\n This dedicated space replaces the\n <a\n href=\"https://near.org/devhub.near/widget/app?page=feed\"\n className=\"text-decoration-underline no-space\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n old activity feed\n </a>\n , making it easier to submit and track funding requests from\n DevDAO, the primary organization behind DevHub. To submit a\n formal proposal, click New Proposal. See our{\" \"}\n <a\n href=\"https://near.org/devhub.near/widget/app?page=community&handle=developer-dao&tab=funding\"\n className=\"text-decoration-underline no-space\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n guidelines\n </a>\n for details. For discussions and brainstorming, please\n utilize the relevant{\" \"}\n <a\n href=\"https://near.org/devhub.near/widget/app?page=communities\"\n className=\"text-decoration-underline no-space\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n communities\n </a>\n .\n </div>\n </p>\n </div>\n <div className=\"mt-4 border rounded-2\">\n {state.data.length > 0 ? (\n <InfiniteScroll\n pageStart={0}\n loadMore={makeMoreItems}\n hasMore={state.aggregatedCount > state.data.length}\n loader={loader}\n useWindow={false}\n threshold={100}\n >\n {renderedItems}\n </InfiniteScroll>\n ) : (\n loader\n )}\n </div>\n </div>\n </div>\n )}\n </div>\n </Container>\n );\n};\n\nreturn FeedPage(props);\n" }, "devhub.page.home": { "": "const HomeSections = [\"hero\", \"explore\", \"connect\", \"participate\", \"support\"];\n\nreturn (\n <>\n {HomeSections.map((it) => (\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.island.${it}`}\n props={{ ...props }}\n />\n ))}\n </>\n);\n" }, "devhub.entity.addon.kanban.Viewer": { "": "const Struct = VM.require(\"devhub.megha19.near/widget/core.lib.struct\");\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nif (!Struct) {\n return <p>Loading modules...</p>;\n}\n\nhref || (href = () => {});\n\nconst { data, handle, permissions } = props;\n\nif (!data || !data?.metadata) {\n return (\n <div\n className=\"d-flex flex-column align-items-center justify-content-center gap-4\"\n style={{ height: 384 }}\n >\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n Please add configuration for your board.\n </h5>\n </div>\n );\n}\n\nreturn (\n <Widget\n src={`devhub.megha19.near/widget/devhub.entity.addon.${data.metadata.type}`}\n props={{\n ...data,\n isSynced: true,\n permissions,\n }}\n />\n);\n" }, "core.adapter.devhub-contract": { "": "function getRootMembers() {\n return Near.view(\"devhub.near\", \"get_root_members\") ?? null;\n}\n\nfunction removeMember(member) {\n return Near.call(\"devhub.near\", \"remove_member\", { member });\n}\n\nfunction hasModerator({ account_id }) {\n return (\n Near.view(\"devhub.near\", \"has_moderator\", { account_id }) ??\n null\n );\n}\n\nfunction createCommunity({ inputs }) {\n return Near.call(\n \"devhub.near\",\n \"create_community\",\n { inputs },\n 250000000000000, // gas (250Tgas)\n Big(4) * Big(10).pow(24) // deposit (4N)\n );\n}\n\nfunction getCommunity({ handle }) {\n return (\n Near.view(\"devhub.near\", \"get_community\", { handle }) ?? null\n );\n}\n\nfunction getFeaturedCommunities() {\n return Near.view(\"devgovgigs.near\", \"get_featured_communities\") ?? null;\n}\n\nfunction setFeaturedCommunities({ handles }) {\n return Near.call(\"devhub.near\", \"set_featured_communities\", {\n handles,\n });\n}\n\nfunction getAccountCommunityPermissions({ account_id, community_handle }) {\n return (\n Near.view(\"devhub.near\", \"get_account_community_permissions\", {\n account_id,\n community_handle,\n }) ?? null\n );\n}\n\nfunction updateCommunity({ handle, community }) {\n return Near.call(\"devhub.near\", \"update_community\", {\n handle,\n community,\n });\n}\n\nfunction deleteCommunity({ handle }) {\n return Near.call(\"devhub.near\", \"delete_community\", { handle });\n}\n\n/**\n * Sets all addons, for configurating tabs\n */\nfunction setCommunityAddons({ handle, addons }) {\n return Near.call(\"devhub.near\", \"set_community_addons\", {\n handle,\n addons,\n });\n}\n\n/**\n * Sets specific addon, for configuring params\n */\nfunction setCommunityAddon({ handle, addon }) {\n return Near.call(\"devhub.near\", \"set_community_addon\", {\n handle,\n community_addon: addon,\n });\n}\n\n/**\n * Gets all available addons, these are controlled by devhub moderators\n */\nfunction getAllAddons() {\n return Near.view(\"devhub.near\", \"get_all_addons\") ?? null;\n}\n\nfunction getAccessControlInfo() {\n return (\n Near.view(\"devhub.near\", \"get_access_control_info\") ?? null\n );\n}\n\nfunction getAllAuthors() {\n return Near.view(\"devgovgigs.near\", \"get_all_authors\") ?? null;\n}\n\nfunction getAllCommunitiesMetadata() {\n return (\n Near.view(\"devhub.near\", \"get_all_communities_metadata\") ?? null\n );\n}\n\nfunction getSocialWithBlockHeight(data) {\n return Near.view(\"social.near\", \"get\", data) ?? null;\n}\n\nfunction getAllLabels() {\n return Near.view(\"devgovgigs.near\", \"get_all_labels\") ?? null;\n}\n\nfunction getPost({ post_id }) {\n return Near.view(\"devgovgigs.near\", \"get_post\", { post_id }) ?? null;\n}\n\nfunction getPostsByAuthor({ author }) {\n return (\n Near.view(\"devgovgigs.near\", \"get_posts_by_author\", { author }) ??\n null\n );\n}\n\nfunction getPostsByLabel({ label }) {\n return (\n Near.view(\"devgovgigs.near\", \"get_posts_by_label\", {\n label,\n }) ?? null\n );\n}\n\nfunction setCommunitySocialDB({ handle, data }) {\n return (\n Near.call(\"devhub.near\", \"set_community_socialdb\", {\n handle,\n data,\n }) ?? null\n );\n}\n\nfunction createDiscussion({ handle, data }) {\n return (\n Near.call(\"devhub.near\", \"create_discussion\", {\n handle,\n data,\n }) ?? null\n );\n}\n\nfunction useQuery(name, params) {\n const initialState = { data: null, error: null, isLoading: true };\n\n const cacheState = useCache(\n () =>\n Near.asyncView(\n \"devgovgigs.near\",\n [\"get\", name].join(\"_\"),\n params ?? {}\n )\n .then((response) => ({\n ...initialState,\n data: response ?? null,\n isLoading: false,\n }))\n .catch((error) => ({\n ...initialState,\n error: props?.error ?? error,\n isLoading: false,\n })),\n\n JSON.stringify({ name, params }),\n { subscribe: false } // NOTE: I'm turning off subscribe to stop the constant re-rendering\n );\n\n return cacheState === null ? initialState : cacheState;\n}\n\nreturn {\n getRootMembers,\n removeMember,\n hasModerator,\n createCommunity,\n getCommunity,\n getFeaturedCommunities,\n setFeaturedCommunities,\n getAccountCommunityPermissions,\n updateCommunity,\n deleteCommunity,\n setCommunityAddons,\n setCommunityAddon,\n getAccessControlInfo,\n getAllAuthors,\n getAllCommunitiesMetadata,\n getAllAddons,\n getAllLabels,\n getPost,\n getPostsByAuthor,\n getPostsByLabel,\n setCommunitySocialDB,\n useQuery,\n};\n" }, "devhub.notification.Right": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nreturn props.proposal === undefined ? (\n \"Loading ...\"\n) : (\n <>\n <a\n className=\"btn btn-outline-dark\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"proposal\",\n id: props.proposal,\n },\n })}\n >\n View DevHub proposal\n </a>\n </>\n);\n" }, "devhub.page.communities": { "": "const { getAllCommunitiesMetadata, createCommunity } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\nif (!getAllCommunitiesMetadata || !createCommunity) {\n return <p>Loading modules...</p>;\n}\n\nconst onCommunitySubmit = (inputs) =>\n createCommunity({\n inputs: {\n ...inputs,\n\n bio_markdown: [\n \"This is a sample text about your community.\",\n \"You can change it on the community configuration page.\",\n ].join(\"\\n\"),\n\n logo_url:\n \"https://ipfs.near.social/ipfs/bafkreibysr2mkwhb4j36h2t7mqwhynqdy4vzjfygfkfg65kuspd2bawauu\",\n\n banner_url:\n \"https://ipfs.near.social/ipfs/bafkreic4xgorjt6ha5z4s5e3hscjqrowe5ahd7hlfc5p4hb6kdfp6prgy4\",\n },\n });\n\nconst [showSpawner, setShowSpawner] = useState(false);\n\nconst [searchKey, setSearchKey] = useState(\"\");\nconst [sort, setSort] = useState(\"\");\n\nconst communitiesMetadata = getAllCommunitiesMetadata();\n\nconst SortedAndFiltered = (searchKey, sortBy) => {\n let communities = (communitiesMetadata ?? []).reverse();\n\n let filtered = [...communities];\n if (searchKey !== \"\") {\n filtered = communities.filter((community) =>\n community.name.toLowerCase().includes(searchKey.toLowerCase())\n );\n }\n\n let sortedCommunities = [...filtered];\n if (sortBy !== \"\") {\n sortedCommunities.sort((a, b) => {\n let nameA = a.name.toLowerCase();\n let nameB = b.name.toLowerCase();\n\n if (nameA < nameB) {\n return -1;\n }\n if (nameA > nameB) {\n return 1;\n }\n return 0;\n });\n\n if (sortBy === \"z-a\") {\n sortedCommunities.reverse();\n }\n }\n\n return sortedCommunities;\n};\n\nconst CardGrid = styled.div`\n width: 100%;\n height: 100%;\n\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 3rem;\n\n @media screen and (max-width: 992px) {\n grid-template-columns: repeat(2, 1fr);\n }\n\n @media screen and (max-width: 768px) {\n display: flex;\n flex-direction: column;\n gap: 2rem;\n }\n`;\n\nconst InputContainer = styled.div`\n display: flex;\n flex-direction: row;\n position: relative;\n width: 100%;\n`;\n\nconst StyledDropdown = styled.div`\n button {\n text-align: left;\n\n &::after {\n position: absolute;\n right: 8px;\n top: 45%;\n transform: translateX(-50%);\n }\n }\n`;\n\nreturn (\n <div className=\"w-100\">\n <Widget src={`devhub.megha19.near/widget/devhub.components.island.banner`} />\n <div style={{ background: \"#f4f4f4\" }}>\n <div\n className=\"d-flex justify-content-between p-4\"\n style={{ backgroundColor: \"\" }}\n >\n <div className=\"d-flex flex-column gap-3 w-100\">\n <h1\n className=\"m-0 fs-4\"\n style={{ color: \"#555555\", fontSize: \"1.5rem\" }}\n >\n Communities\n </h1>\n\n <div className=\"d-flex col-12 flex-column flex-sm-row gap-4 justify-content-between align-items-center\">\n <div className=\"d-flex flex-column flex-sm-row align-items-center gap-4 col-12 col-sm-6\">\n <InputContainer className=\"border rounded-2\">\n <div className=\"position-absolute d-flex ps-3 flex-column h-100 justify-center\">\n <i class=\"bi bi-search m-auto\"></i>\n </div>\n <input\n type=\"search\"\n className=\"ps-5 form-control border border-0\"\n value={searchKey ?? \"\"}\n onChange={(e) => setSearchKey(e.target.value)}\n placeholder={props.placeholder ?? `Search by name`}\n />\n </InputContainer>\n <div class=\"dropdown w-100\">\n <StyledDropdown>\n <button\n class=\"btn dropdown-toggle border rounded-2 bg-white w-100\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n >\n Sort: {sort?.toUpperCase() || \"Latest\"}\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end dropdown-menu-lg-start px-2 shadow\">\n <li\n onClick={() => setSort(\"\")}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n >\n Latest\n </li>\n <li\n onClick={() => setSort(\"a-z\")}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n >\n A-Z\n </li>\n <li\n onClick={() => setSort(\"z-a\")}\n class=\"dropdown-item link-underline link-underline-opacity-0\"\n >\n Z-A\n </li>\n </ul>\n </StyledDropdown>\n </div>\n </div>\n {context.accountId && (\n <div className=\"d-flex flex-column justify-content-center align-self-end\">\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.components.molecule.CommunityControl\"\n }\n props={{\n title: \"Community\",\n onClick: () => setShowSpawner(!showSpawner),\n }}\n />\n </div>\n )}\n </div>\n </div>\n </div>\n <div className=\"d-flex flex-wrap align-content-start gap-4 py-2 px-4 w-100 h-100\">\n {showSpawner && (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.community.Spawner\"\n props={{\n data: null,\n onSubmit: onCommunitySubmit,\n onCancel: () => setShowSpawner(false),\n }}\n />\n )}\n <CardGrid>\n {searchKey === \"\" && sort === \"\"\n ? (communitiesMetadata ?? []).reverse().map((communityMetadata) => (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.community.Card\"\n props={{\n format: \"small\",\n isBannerEnabled: false,\n metadata: communityMetadata,\n }}\n />\n ))\n : SortedAndFiltered(searchKey, sort).map((communityMetadata) => (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.community.Card\"\n props={{\n format: \"small\",\n isBannerEnabled: false,\n metadata: communityMetadata,\n }}\n />\n ))}\n </CardGrid>\n </div>\n </div>\n </div>\n);\n" }, "devhub.entity.addon.blog.Card": { "": "function Card({ data }) {\n const { category, title, subtitle, date } = data;\n\n const Container = styled.div`\n min-height: 12.5rem;\n height: 100%;\n display: flex;\n padding: 1rem;\n flex-direction: column;\n align-items: flex-start;\n gap: 1rem;\n flex-shrink: 0;\n\n border-radius: 1rem;\n border: 1px solid rgba(129, 129, 129, 0.3);\n background: #fffefe;\n\n h5 {\n margin: 0;\n color: #151515;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 110%; /* 39.6px */\n }\n\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: 1rem;\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: 16px;\n font-style: normal;\n font-weight: 400;\n line-height: 20px; /* 125% */\n margin-top: auto;\n }\n\n p {\n margin: 0;\n color: #151515;\n font-size: 1rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n }\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 <Container id={`blog-card-${title}`}>\n {category && <span className=\"category\">{category}</span>}\n <h5>{title}</h5>\n <p>{subtitle}</p>\n <span className=\"date\">{formattedDate}</span>\n </Container>\n );\n}\n\nreturn { Card };\n" }, "DevGov.Notification.Item.Right": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nreturn props.post === undefined ? (\n \"Loading ...\"\n) : (\n <>\n <a\n className=\"btn btn-outline-dark\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"post\",\n id: props.post,\n },\n })}\n >\n View DevHub post\n </a>\n </>\n);\n" }, "DevGov.Notification.Item.Left": { "": "const { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\") || (() => {});\n\nif (!props.type) {\n return \"Loading ...\";\n}\n\nconst type = props.type.split(\"/\")[1];\nreturn props.type ? (\n <>\n {type == \"like\"\n ? \"liked your\"\n : type == \"reply\"\n ? \"replied to your\"\n : type == \"edit\"\n ? \"edited your\"\n : type == \"mention\"\n ? \"mentioned you in their\"\n : \"???\"}{\" \"}\n <a\n className=\"fw-bold text-muted\"\n href={href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: {\n page: \"post\",\n id: props.post,\n },\n })}\n >\n DevHub post\n </a>\n </>\n) : (\n \"Loading ...\"\n);\n" }, "devhub.entity.addon.kanban.Configurator": { "": "const Struct = VM.require(\"devhub.megha19.near/widget/core.lib.struct\");\n\nif (!Struct) {\n return <p>Loading modules...</p>;\n}\nconst { useQuery } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\nconst { uuid, withUUIDIndex } = VM.require(\n \"devhub.megha19.near/widget/core.lib.uuid\"\n);\n\nuuid || (uuid = () => {});\nwithUUIDIndex || (withUUIDIndex = () => {});\nuseQuery || (useQuery = () => {});\n\nconst AttractableDiv = styled.div`\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\nconst settings = {\n maxColumnsNumber: 10,\n};\n\nconst KanbanPostBoardBasicInfoSchema = {\n title: { label: \"Title\", order: 1, placeholder: \"Enter board title\" },\n description: {\n label: \"Description\",\n order: 2,\n placeholder: \"Enter board description\",\n },\n};\n\nconst KanbanPostBoardTicketFeaturesSchema = {\n author: { label: \"Author\" },\n like_count: { label: \"Likes\" },\n approved_sponsorship_value: { label: \"Funding amount\" },\n sponsorship_supervisor: { label: \"Supervisor/Sponser\" },\n tags: { label: \"Tags\" },\n type: { label: \"Post type\" },\n};\n\nconst KanbanPostBoardDefaults = {\n metadata: {\n id: uuid(),\n type: \"kanban.post_board\",\n title: \"\",\n description: \"\",\n ticket: {\n type: \"kanban.post_ticket\",\n features: {\n author: true,\n like_count: true,\n approved_sponsorship_value: true,\n sponsorship_supervisor: true,\n tags: true,\n type: true,\n },\n sortBy: \"\",\n },\n },\n payload: {\n columns: {},\n },\n};\n\nconst toMigrated = ({ config, metadata, payload }) => ({\n metadata: {\n ...KanbanPostBoardDefaults.metadata,\n ...metadata,\n ticket: {\n ...KanbanPostBoardDefaults.metadata.ticket,\n ...metadata.ticket,\n features: {\n ...KanbanPostBoardDefaults.metadata.ticket.features,\n ...metadata.ticket.features,\n },\n },\n },\n payload: {\n ...KanbanPostBoardDefaults.payload,\n ...payload,\n ...config,\n },\n});\n\nconst sortByOptions = [\n { label: \"None\", value: \"none\" },\n { label: \"Amount: High to Low\", value: \"descending-amount\" },\n { label: \"Amount: Low to High\", value: \"ascending-amount\" },\n { label: \"Date: Newest to Oldest\", value: \"descending-date\" },\n { label: \"Date: Oldest to Newest\", value: \"ascending-date\" },\n { label: \"Author: A-Z\", value: \"ascending-author\" },\n { label: \"Author: Z-A\", value: \"descending-author\" },\n { label: \"Sponsor/Supervisor: A-Z\", value: \"ascending-sponsor\" },\n { label: \"Sponsor/Supervisor: Z-A\", value: \"descending-sponsor\" },\n { label: \"Most Likes\", value: \"descending-likes\" },\n { label: \"Fewest Likes\", value: \"ascending-likes\" },\n];\n\nconst KanbanViewConfigurator = ({ handle, data, permissions, onSubmit }) => {\n const tags = useCache(\n () =>\n Near.asyncView(\"devhub.near\", \"get_all_labels\").then(\n (res) => res\n ),\n handle,\n {\n subscribe: false,\n }\n );\n\n if (!data) {\n return (\n <div class=\"alert alert-danger\" role=\"alert\">\n Loading...\n </div>\n );\n }\n const initialFormState = Struct.pick(\n data.metadata === undefined ? {} : toMigrated(data),\n [\"metadata\", \"payload\"]\n );\n\n const [formState, setForm] = useState(initialFormState);\n const [showPreview, setPreview] = useState(false);\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 ?? {},\n path,\n (node) => transformFn(node)\n );\n setForm((prevFormState) => ({\n ...prevFormState,\n ...updatedValues,\n }));\n };\n\n const formReset = () => {\n setForm(initialFormState);\n };\n\n const newViewInit = () => {\n setForm(KanbanPostBoardDefaults);\n };\n\n const columnsCreateNew = ({ lastKnownValue }) =>\n Object.keys(lastKnownValue).length < settings.maxColumnsNumber\n ? {\n ...(lastKnownValue ?? {}),\n ...withUUIDIndex({ tag: \"\", title: \"New column\", description: \"\" }),\n }\n : lastKnownValue;\n\n const columnsDeleteById =\n (id) =>\n ({ lastKnownValue }) =>\n Object.fromEntries(\n Object.entries(lastKnownValue).filter(([columnId]) => columnId !== id)\n );\n\n const onCancel = () => {\n formReset();\n };\n\n const onSave = () => onSubmit(formState);\n\n const formElement = (\n <div className=\"card p-2\">\n <div className=\"card-body d-flex flex-column gap-3\">\n <div className=\"d-flex flex-column flex-lg-row align-items-stretch w-100\">\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.organism.Configurator`}\n props={{\n heading: \"Basic information\",\n externalState: formState.metadata,\n isActive: true,\n isEmbedded: true,\n isUnlocked: permissions.can_configure,\n onChange: formUpdate({ path: [\"metadata\"] }),\n schema: KanbanPostBoardBasicInfoSchema,\n hideSubmitBtn: true,\n }}\n />\n </div>\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2 flex-grow-1\">\n <span\n className=\"d-flex justify-content-between align-items-center gap-3 w-100\"\n style={{ fontWeight: 500, fontSize: \"16\" }}\n >\n Fields to display\n </span>\n <div>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.organism.Configurator`}\n props={{\n heading: \"Card fields\",\n classNames: { root: \"w-auto h-auto\" },\n externalState: formState.metadata.ticket.features,\n isActive: true,\n isEmbedded: true,\n isUnlocked: permissions.can_configure,\n onChange: formUpdate({\n path: [\"metadata\", \"ticket\", \"features\"],\n }),\n schema: KanbanPostBoardTicketFeaturesSchema,\n style: { minWidth: \"36%\" },\n hideSubmitBtn: true,\n }}\n />\n </div>\n </div>\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2 flex-grow-1\">\n <span\n className=\"d-flex justify-content-between align-items-center gap-3 w-100\"\n style={{ fontWeight: 500, fontSize: \"16\" }}\n >\n Sort by\n </span>\n <div>\n <div className=\"input-group\">\n <select\n className=\"form-select border border-1\"\n value={formState.metadata.ticket.sortBy}\n onChange={formUpdate({\n path: [\"metadata\", \"ticket\", \"sortBy\"],\n })}\n aria-label={label}\n >\n <option value=\"none\" disabled hidden>\n None\n </option>\n {sortByOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n </div>\n\n <div className=\"d-flex align-items-center justify-content-between w-100 mb-1\">\n <span className=\"d-inline-flex gap-2 m-0\">\n <i className=\"bi bi-list-task\" />\n <span>{`Columns ( max. ${settings.maxColumnsNumber} )`}</span>\n </span>\n </div>\n\n <div className=\"d-flex flex-column align-items-center gap-4 w-100\">\n {Object.values(formState.payload.columns ?? {}).map(\n ({ id, description, tag, title }) => (\n <AttractableDiv\n className=\"d-flex gap-3 rounded-4 border p-3 w-100\"\n key={`column-${id}-configurator`}\n >\n <div className=\"d-flex flex-column gap-1 w-100\">\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n key: `column-${id}-title`,\n label: \"Title\",\n onChange: formUpdate({\n path: [\"payload\", \"columns\", id, \"title\"],\n }),\n placeholder: \"Enter column title\",\n value: title,\n }}\n />\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Input`}\n props={{\n className: \"flex-grow-1\",\n key: `column-${id}-description`,\n label: \"Description\",\n onChange: formUpdate({\n path: [\"payload\", \"columns\", id, \"description\"],\n }),\n placeholder: \"Enter a brief description for the column\",\n value: description,\n }}\n />\n <div className=\"d-flex flex-column flex-1 align-items-start justify-content-evenly gap-1 p-2 flex-grow-1\">\n <span className=\"d-flex justify-content-between align-items-center gap-3 w-100\">\n Enter a tag to filter posts in this column\n </span>\n <div className=\"w-100\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.feature.post-search.by-tag\"\n props={{\n tag: tag,\n onTagSearch: formUpdate({\n path: [\"payload\", \"columns\", id, \"tag\"],\n }),\n }}\n />\n </div>\n </div>\n </div>\n\n <div\n className=\"d-flex flex-column gap-3 border-start p-3 pe-0\"\n style={{ marginTop: -16, marginBottom: -16 }}\n >\n <button\n className=\"btn btn-outline-danger\"\n onClick={formUpdate({\n path: [\"payload\", \"columns\"],\n via: columnsDeleteById(id),\n })}\n title=\"Delete column\"\n >\n <i className=\"bi bi-trash-fill\" />\n </button>\n </div>\n </AttractableDiv>\n )\n )}\n </div>\n <div className=\"d-flex gap-3 justify-content-between w-100 mt-2 flex-wrap flex-sm-nowrap\">\n <div style={{ flex: \"none\" }}>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"btn-sm btn-outline-secondary\",\n },\n label: \"New column\",\n disabled:\n Object.keys(formState.payload.columns).length >=\n settings.maxColumnsNumber,\n icon: { type: \"bootstrap_icon\", variant: \"bi-plus-lg\" },\n onClick: formUpdate({\n path: [\"payload\", \"columns\"],\n via: columnsCreateNew,\n }),\n }}\n />\n </div>\n <div className=\"d-flex gap-3 justify-content-end w-100\">\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"d-flex btn btn-outline-danger shadow-none border-0\",\n },\n isHidden: typeof onCancel !== \"function\",\n label: \"Cancel\",\n onClick: onCancel,\n }}\n />\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: { root: \"btn btn-success\" },\n disabled: form.isSynced,\n icon: {\n type: \"svg_icon\",\n variant: \"floppy_drive\",\n width: 14,\n height: 14,\n },\n isHidden: typeof onSave !== \"function\",\n label: \"Save\",\n onClick: onSave,\n }}\n />\n </div>\n </div>\n </div>\n </div>\n );\n\n return (\n <div\n className=\"d-flex flex-column gap-4 w-100\"\n style={{ maxWidth: \"100%\" }}\n >\n <ul className=\"nav nav-tabs\" id=\"editPreviewTabs\" role=\"tablist\">\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className={`nav-link ${!showPreview ? \"active\" : \"\"}`}\n id=\"edit-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#edit\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"edit\"\n aria-selected=\"true\"\n onClick={() => setPreview(false)}\n >\n Edit\n </button>\n </li>\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className={`nav-link ${showPreview ? \"active\" : \"\"}`}\n id=\"preview-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#preview\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"preview\"\n aria-selected=\"false\"\n onClick={() => setPreview(true)}\n >\n Preview\n </button>\n </li>\n </ul>\n {showPreview ? (\n <div>\n <Widget\n src={`devhub.megha19.near/widget/devhub.entity.addon.kanban.Viewer`}\n props={{\n data: formState,\n }}\n />\n </div>\n ) : (\n <div className={[\"d-flex flex-column gap-4 w-100\"].join(\" \")}>\n <div className=\"d-flex align-items-center justify-content-between gap-3 w-100\">\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n <i className=\"bi bi-gear-wide-connected\" />\n <span>Kanban board configuration</span>\n </h5>\n </div>\n {Object.keys(formState.metadata ?? {}).length > 0 && formElement}\n </div>\n )}\n {!Object.keys(formState.metadata ?? {}).length && (\n <div\n className=\"d-flex flex-column align-items-center justify-content-center gap-4\"\n style={{ height: 384 }}\n >\n <h5 className=\"h5 d-inline-flex gap-2 m-0\">\n This community doesn't have a kanban board\n </h5>\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.molecule.Button`}\n props={{\n icon: { type: \"bootstrap_icon\", variant: \"bi-kanban-fill\" },\n isHidden: !permissions.can_configure,\n label: \"Create kanban board\",\n onClick: newViewInit,\n }}\n />\n </div>\n )}\n </div>\n );\n};\n\nreturn KanbanViewConfigurator(props);\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.megha19.near/widget/devhub.components.molecule.MarkdownViewer\"\n }\n props={{ text: content }}\n />\n </Content>\n </Container>\n );\n}\n" }, "devhub.page.about": { "": "const PageHeader = styled.h2`\n color: #555555;\n font-size: 24px;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.24px;\n\n width: 100%;\n padding: 1rem 3.125rem;\n margin-left: auto;\n //background: #fff;\n\n margin-top: 1.5rem;\n margin-bottom: 1.5rem;\n\n @media screen and (max-width: 786px) {\n padding: 1rem;\n }\n`;\n\nconst Section = styled.div`\n padding: 1.5rem 8rem;\n display: flex;\n flex-direction: column;\n\n display: flex;\n flex-direction: column;\n\n gap: 2rem;\n\n @media screen and (max-width: 786px) {\n padding: 1rem;\n }\n\n h2 {\n color: #151515;\n font-size: 2rem;\n font-style: normal;\n font-weight: 700;\n line-height: 2rem; /* 55.556% */\n\n margin: 1rem;\n margin-left: 0;\n }\n\n p {\n color: #151515;\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 400;\n line-height: 150%;\n margin-bottom: 1rem;\n }\n\n h3 {\n color: #151515;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%;\n\n margin-top: 1rem;\n }\n`;\n\nreturn (\n <>\n <Widget src={`devhub.megha19.near/widget/devhub.components.island.banner`} />\n <Section>\n <h2>\n What is <span style={{ color: \"#00EC97\" }}>near</span>/dev/hub?\n </h2>\n\n <div>\n <p>\n DevHub brings together individuals, projects, and organizations to\n build a decentralized NEAR developer ecosystem. We provide social\n structure and tools to fuel innovation, collaboration, and community\n within the NEAR ecosystem. Whether you’re a blockchain developer or\n advocate for the open web, DevHub is your gateway to making a\n meaningful impact on NEAR and beyond.\n </p>\n\n <h3>Mission</h3>\n <p>\n Build a self-sufficient developer community to enable a thriving NEAR\n ecosystem.\n </p>\n\n <h3>Values</h3>\n <p>\n <ul>\n <li>\n <b>Decentralized</b>: We are building together.\n </li>\n <li>\n <b>Transparent</b>: All decision making and communication is open.\n </li>\n <li>\n <b>Self-sufficient</b>: We do not critically depend on any single\n entity.\n </li>\n <li>\n <b>Robust</b>: Any role or team can be duplicated or replaced.\n </li>\n <li>\n <b>High-velocity</b>: We need to move faster than classic OSS.\n </li>\n </ul>\n </p>\n\n <h3>Scope</h3>\n <p>\n DevHub covers a wide range of areas to support the NEAR developer\n ecosystem, including:\n <ul>\n <li>\n <b>Developer Tooling</b>: Contributing code to the NEAR Platform\n (blockchain and devtools) and facilitating technical decisions\n with recognized experts based on the needs of the community and\n ecosystem.\n </li>\n <li>\n <b>Developer Relations</b>: Engaging with the community across\n various social channels, gathering feedback, and improving\n documentation.\n </li>\n <li>\n <b>Deep-Tech Awareness</b>: Working with marketing partners to\n create awareness on interesting projects and technology areas.\n </li>\n <li>\n <b>Events and Hackathons</b>: Organizing events and empowering\n community leaders with resources to grow their local communities.\n </li>\n <li>\n <b>Education</b>: Partnering with universities across the globe to\n support students and scholars in exploring Web3 technologies\n </li>\n <li>\n <b>Platform</b>: Developing DevHub platform as a product to enable\n communities to collaborate and support each other.\n </li>\n </ul>\n </p>\n\n <h3>Our Contributors</h3>\n <p>\n DevHub operates through DevDAO, which provides social structures to\n support developers. Within DevDAO, we have a dedicated core team of\n moderators and community contributors who work across the key areas\n above. We welcome contributions from any community members who want to\n join us in building our ecosystem and their own skills!\n </p>\n\n <h3>Our Platform</h3>\n <p>\n Our main tool for interfacing is the DevHub Platform, where you can\n connect with others, share ideas and solutions, and access resources\n and support. You can also find communities working on a variety of\n areas, from protocol development to tooling and documentation.\n </p>\n\n <h3>Join Us in Shaping NEAR’s Future</h3>\n <p>\n Regardless of your background or where you are on your developer\n journey, we’re happy you’re here! We hope you’ll explore, find your\n people, and discover paths to contribute that are most gratifying for\n you.\n <br />\n Let’s build the open web together.\n </p>\n </div>\n </Section>\n </>\n);\n" }, "devhub.entity.addon.telegram.Configurator": { "": "const { Tile } =\n VM.require(\"devhub.megha19.near/widget/devhub.components.molecule.Tile\") ||\n (() => <></>);\n\nif (!Tile) {\n return <div>Loading...</div>;\n}\n\nconst { data, onSubmit } = props;\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n`;\n\nconst Item = styled.div`\n padding: 10px;\n margin: 5px;\n display: flex;\n align-items: center;\n flex-direction: row;\n gap: 10px;\n`;\n\nconst EditableField = styled.input`\n flex: 1;\n`;\nconst initialData = data.handles;\nconst [handles, setHandles] = useState(initialData || []);\nconst [newItem, setNewItem] = useState(\"\");\n\nconst handleAddItem = () => {\n if (newItem) {\n setHandles([...handles, newItem]);\n setNewItem(\"\");\n }\n};\n\nconst handleDeleteItem = (index) => {\n const updatedData = [...handles];\n updatedData.splice(index, 1);\n setHandles(updatedData);\n};\n\nconst handleSubmit = () => {\n onSubmit({ handles: handles.map((handle) => handle.trim()) });\n};\n\nreturn (\n <Tile className=\"p-3\">\n <Container>\n {handles.map((item, index) => (\n <Item key={index}>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: item,\n placeholder: \"Telegram Handle\",\n inputProps: {\n prefix: \"https://t.me/\",\n disabled: true,\n },\n }}\n />\n </div>\n <button\n className=\"btn btn-outline-danger\"\n onClick={() => handleDeleteItem(index)}\n >\n <i className=\"bi bi-trash-fill\" />\n </button>\n </Item>\n ))}\n <Item>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n onChange: (e) => setNewItem(e.target.value),\n value: newItem,\n placeholder: \"Telegram Handle\",\n inputProps: {\n prefix: \"https://t.me/\",\n },\n }}\n />\n </div>\n <button\n className=\"btn btn-success\"\n onClick={handleAddItem}\n disabled={newItem === \"\"}\n >\n <i className=\"bi bi-plus\" />\n </button>\n </Item>\n <div\n className={\"d-flex align-items-center justify-content-end gap-3 mt-4\"}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-success\" },\n disabled: initialData === handles,\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Submit\",\n onClick: handleSubmit,\n }}\n />\n </div>\n </Container>\n </Tile>\n);\n" }, "devhub.entity.addon.kanban.post_ticket": { "": "const columnId = props.columnId;\n\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\nhref || (href = () => {});\n\nconst AttractableDiv = styled.div`\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\nconst iconsByPostType = {\n Idea: \"bi-lightbulb\",\n Comment: \"bi-chat\",\n Solution: \"bi-rocket\",\n Attestation: \"bi-check-circle\",\n Sponsorship: \"bi-cash-coin\",\n};\n\nfunction getToken(token) {\n let amountUnit = \"\";\n if (typeof token === \"string\") {\n amountUnit = token;\n } else if (typeof token === \"object\") {\n const address = Object.values(token)?.[0]?.address ?? \"\";\n const ftMetadata = Near.view(address, \"ft_metadata\", {});\n if (ftMetadata !== null) {\n amountUnit = ftMetadata?.symbol;\n }\n }\n return amountUnit;\n}\nconst KanbanPostTicket = ({ metadata, data }) => {\n if (!data) return <div>Loading ...</div>;\n\n const {\n post_type,\n name,\n description,\n labels: tags,\n requested_sponsorship_amount,\n requested_sponsorship_token,\n requested_sponsor,\n amount,\n sponsorship_token,\n supervisor,\n } = data.snapshot;\n\n const features = {\n ...metadata.features,\n approved_sponsorship_value:\n post_type === \"Sponsorship\" &&\n metadata.features.approved_sponsorship_value,\n sponsorship_supervisor:\n post_type === \"Sponsorship\" && metadata.features.sponsorship_supervisor,\n };\n\n const footer = (\n <div className=\"card-header d-flex justify-content-between gap-3 align-items-center\">\n {features.like_count && (\n <div className=\"d-flex gap-2 align-items-center\">\n <Widget\n src={`devhub.megha19.near/widget/devhub.components.atom.Icon`}\n props={{\n type: \"bootstrap_icon\",\n variant: \"bi-heart-fill\",\n }}\n />\n {data.likes.length}\n </div>\n )}\n <div className=\"d-flex justify-content-end w-100\">\n <a\n className=\"card-link\"\n href={href(\"Post\", { id: data.id })}\n role=\"button\"\n target=\"_blank\"\n title=\"Open in new tab\"\n >\n <i className=\"bi bi-share\" />\n </a>\n </div>\n </div>\n );\n\n const titleArea = (\n <span>\n {features.type ? (\n <i className={`bi ${iconsByPostType[post_type]}`} />\n ) : null}\n <span>{name}</span>\n </span>\n );\n\n const sponsorshipValue = (\n <span className=\"d-flex flex-nowrap gap-1\">\n <span>{requested_sponsorship_amount ?? amount}</span>\n <span>{requested_sponsorship_token ?? getToken(sponsorship_token)}</span>\n </span>\n );\n\n const requestedSponsor = (\n <Widget\n className=\"flex-wrap\"\n src={`neardevgov.near/widget/ProfileLine`}\n props={{\n accountId: requested_sponsor ?? supervisor,\n hideAccountId: true,\n tooltip: true,\n }}\n />\n );\n\n const descriptionArea =\n post_type === \"Comment\" ? (\n <div\n style={{ maxHeight: \"6em\", wordBreak: \"break-all\", overflow: \"hidden\" }}\n >\n <Markdown className=\"card-text\" text={description} />\n </div>\n ) : null;\n\n const tagList =\n Array.isArray(tags) && features.tags ? (\n <div className=\"d-flex flex-wrap gap-2 m-0\">\n {(tags ?? []).map((tag) => (\n <a href={href(\"Feed\", { tag })} key={tag}>\n <span className=\"badge text-bg-primary me-1\">{tag}</span>\n </a>\n ))}\n </div>\n ) : null;\n\n const showFunding = features.approved_sponsorship_value;\n const showSponsor = features.sponsorship_supervisor;\n\n return (\n <AttractableDiv className=\"card\">\n <div\n className=\"card-body d-flex flex-column gap-2\"\n style={{ fontSize: 14 }}\n >\n <div className=\"d-flex justify-content-between gap-2\">\n <span className=\"card-text gap-2\">{titleArea}</span>\n {features.author && (\n <a\n href={`https://near.org/mob.near/widget/ProfilePage?accountId=${data.author_id}`}\n s\n style={{ minWidth: 20 }}\n >\n <Widget\n src=\"mob.near/widget/ProfileImage\"\n props={{\n metadata,\n accountId: data.author_id,\n widgetName,\n style: { height: \"1.8em\", width: \"1.8em\", minWidth: \"1.8em\" },\n }}\n />\n </a>\n )}\n </div>\n {descriptionArea}\n {showFunding && (\n <span className=\"d-flex flex-wrap gap-2\">\n <span>Amount:</span>\n {sponsorshipValue}\n </span>\n )}\n {showSponsor && (\n <div className=\"d-flex flex-wrap gap-2\">\n <span>{`${\n post_type === \"Solution\" ? \"Sponsor\" : \"Supervisor\"\n }:`}</span>{\" \"}\n {requestedSponsor}{\" \"}\n </div>\n )}\n {tagList}\n </div>\n {footer}\n </AttractableDiv>\n );\n};\n\nreturn KanbanPostTicket(props);\n" }, "core.lib.struct": { "": "/**\n * Deeply updates an object's field based on the given path and transformation function.\n *\n * @param {Object} target - The target object to update.\n * @param {Array} path - The path to the field to update.\n * @param {Function} transform - The transformation function to apply.\n * @returns {Object} - The updated object.\n */\nconst deepFieldUpdate = (target, path, transform) => {\n if (path.length === 0) {\n return transform(target);\n }\n\n const [nextNodeKey, ...remainingPath] = path;\n\n return {\n ...target,\n [nextNodeKey]: deepFieldUpdate(\n target[nextNodeKey] ?? {},\n remainingPath,\n transform\n ),\n };\n};\n\n/**\n * Checks if two inputs (objects or arrays) are deeply equal.\n *\n * @param {Object|Array} input1 - The first input.\n * @param {Object|Array} input2 - The second input.\n * @returns {boolean} - True if the inputs are deeply equal, false otherwise.\n */\nconst isEqual = (input1, input2) => {\n const input1Str = JSON.stringify(input1);\n const input2Str = JSON.stringify(input2);\n return input1Str === input2Str;\n};\n\n/**\n * Creates a new object with sorted keys based on the input object.\n *\n * @param {Object} input - The input object.\n * @returns {Object} - A new object with sorted keys.\n */\nconst toOrdered = (input) => {\n if (typeof input !== \"object\" || input === null) {\n return {};\n }\n\n return Object.keys(input)\n .sort()\n .reduce((output, key) => ({ ...output, [key]: input[key] }), {});\n};\n\n/**\n * Picks specified keys from an object and returns a new object with those keys.\n *\n * @param {Object} sourceObject - The source object to pick keys from.\n * @param {Array} keysToPick - An array of keys to pick.\n * @returns {Object} - A new object containing the selected keys.\n */\nconst pick = (sourceObject, keysToPick) => {\n if (typeof sourceObject !== \"object\" || sourceObject === null) {\n return {};\n }\n\n return Object.fromEntries(\n Object.entries(sourceObject).filter(([key, _]) => keysToPick.includes(key))\n );\n};\n\n/**\n * Checks if the input matches the expected type.\n *\n * @param {Object} input - The input to check.\n * @returns {boolean} - True if the input is an object and not an array or null, false otherwise.\n */\nconst typeMatch = (input) =>\n input !== null && typeof input === \"object\" && !Array.isArray(input);\n\nconst defaultFieldUpdate = ({\n input,\n lastKnownValue,\n params: { arrayDelimiter },\n}) => {\n switch (typeof input) {\n case \"boolean\":\n return input;\n\n case \"object\": {\n if (Array.isArray(input) && typeof lastKnownValue === \"string\") {\n return input.join(arrayDelimiter ?? \",\");\n } else {\n return Array.isArray(lastKnownValue)\n ? [...lastKnownValue, ...input]\n : { ...lastKnownValue, ...input };\n }\n }\n\n case \"string\":\n return Array.isArray(lastKnownValue)\n ? input.split(arrayDelimiter ?? \",\").map((string) => string.trim())\n : input;\n\n default: {\n if ((input ?? null) === null) {\n switch (typeof lastKnownValue) {\n case \"boolean\":\n return !lastKnownValue;\n\n default:\n return lastKnownValue;\n }\n } else return input;\n }\n }\n};\n\nreturn {\n deepFieldUpdate,\n isEqual,\n pick,\n toOrdered,\n typeMatch,\n defaultFieldUpdate,\n};\n" }, "devhub.entity.community.Discussions": { "": "const NEW_DISCUSSION_POSTED_CONTENT_STORAGE_KEY =\n \"new_discussion_posted_content\";\nconst { handle } = props;\nconst { getCommunity, setCommunitySocialDB } = VM.require(\n \"devhub.megha19.near/widget/core.adapter.devhub-contract\"\n);\n\ngetCommunity = getCommunity || (() => <></>);\nsetCommunitySocialDB = setCommunitySocialDB || (() => <></>);\n\nconst communityData = getCommunity({ handle });\n\nconst MainContent = styled.div`\n padding-left: 2rem;\n flex: 3;\n @media screen and (max-width: 960px) {\n padding-left: 0rem;\n }\n .post:hover {\n background-color: inherit !important;\n }\n`;\n\nconst SidebarContainer = styled.div`\n flex: 1;\n`;\n\nconst Heading = styled.div`\n font-size: 19px;\n font-weight: 600;\n`;\n\nconst SubHeading = styled.div`\n font-size: 15px;\n font-weight: 600;\n`;\n\nconst Container = styled.div`\n flex-wrap: no-wrap;\n max-width: 100%;\n\n .max-width-100 {\n max-width: 100%;\n }\n @media screen and (max-width: 960px) {\n flex-wrap: wrap;\n }\n\n .card {\n border-radius: 1rem !important;\n }\n\n .display-none {\n display: none;\n }\n`;\n\nconst Tag = styled.div`\n border-top-right-radius: 50px;\n border-bottom-right-radius: 50px;\n border-top-left-radius: 50px;\n border-bottom-left-radius: 50px;\n padding-inline: 0.8rem;\n padding-block: 0.3rem;\n display: flex;\n gap: 0.5rem;\n border-width: 1px;\n border-style: solid;\n font-size: 14px;\n color: rgba(0, 236, 151, 1);\n font-weight: 800;\n`;\n\nconst [sort, setSort] = useState(\"desc\");\nconst [isTransactionFinished, setIsTransactionFinished] = useState(false);\n\nconst discussionsAccountId =\n \"discussions.\" + handle + \".community.devhub.near\";\n\nfunction checkIfReposted(blockHeight) {\n Near.asyncView(\"social.near\", \"get\", {\n keys: [`${discussionsAccountId}/index/**`],\n })\n .then((response) => {\n const repost = response[discussionsAccountId].index.repost;\n\n if (repost && repost.indexOf(`\"blockHeight\":${blockHeight}`) > -1) {\n setIsTransactionFinished(true);\n } else {\n setTimeout(() => checkIfReposted(), 500);\n }\n })\n .catch((error) => {\n console.error(\n \"DevHub Error [Discussions]: checkIfReposted failed\",\n error\n );\n });\n}\n\nfunction repostOnDiscussions(blockHeight) {\n Near.call([\n {\n contractName: \"devhub.near\",\n methodName: \"create_discussion\",\n args: {\n handle,\n block_height: blockHeight,\n },\n gas: Big(10).pow(14),\n },\n ]);\n checkIfReposted(blockHeight);\n}\n\nasync function checkHashes() {\n if (props.transactionHashes) {\n asyncFetch(\"https://rpc.mainnet.near.org\", {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: \"dontcare\",\n method: \"tx\",\n params: [props.transactionHashes, context.accountId],\n }),\n }).then((transaction) => {\n if (transaction !== null) {\n const transaction_method_name =\n transaction?.body?.result?.transaction?.actions[0].FunctionCall\n .method_name;\n\n if (transaction_method_name === \"set\") {\n getBlockHeightAndRepost();\n }\n\n // show the latest created post to user\n if (transaction_method_name === \"create_discussion\") {\n console.log(\"Discussions created in the last call, show it to user.\");\n }\n }\n });\n }\n}\n\nfunction getBlockHeightAndRepost() {\n const newDiscussionPostedContent = Storage.get(\n NEW_DISCUSSION_POSTED_CONTENT_STORAGE_KEY\n );\n console.log(\"new discussion content\", newDiscussionPostedContent);\n\n Near.asyncView(\"social.near\", \"get\", {\n keys: [`${context.accountId}/post/**`],\n options: {\n with_block_height: true,\n },\n })\n .then((response) => {\n const post_main = response[context.accountId].post.main;\n const content = post_main[\"\"];\n if (content === newDiscussionPostedContent) {\n const blockHeight = post_main[\":block\"];\n console.log(\"content matches\", blockHeight, post_main);\n repostOnDiscussions(blockHeight);\n } else {\n console.log(\n \"content does not match (yet)\",\n post_main,\n newDiscussionPostedContent\n );\n setTimeout(() => getBlockHeightAndRepost(), 500);\n }\n })\n .catch((error) => {\n console.log(\n \"DevHub Error [Discussions]: getBlockHeightAndRepost failed\",\n error\n );\n });\n}\n\ncheckHashes();\n\nreturn (\n <div className=\"w-100\" style={{ maxWidth: \"100%\" }}>\n <Container className=\"d-flex gap-3 m-3 pl-2\">\n <MainContent className=\"max-width-100\">\n <div className=\"d-flex flex-column gap-4\">\n {context.accountId && (\n <div className=\"card p-4\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.entity.community.Compose\"}\n props={{\n isFinished: () => isTransactionFinished,\n onSubmit: (v) => {\n console.log(\"ON SUBMIT\");\n Storage.set(\n NEW_DISCUSSION_POSTED_CONTENT_STORAGE_KEY,\n v.post.main\n );\n\n Social.set(v, {\n force: true,\n onCommit: () => {\n getBlockHeightAndRepost();\n },\n });\n },\n profileAccountId: context.accountId,\n }}\n />\n </div>\n )}\n <div className=\"d-flex flex-wrap justify-content-between\">\n <Heading>Discussions</Heading>\n <div className={\"d-flex align-items-center gap-2\"}>\n <select\n name=\"sort\"\n id=\"sort\"\n class=\"form-select\"\n value={sort}\n onChange={(e) => {\n setSort(e.target.value);\n }}\n >\n <option selected value=\"desc\">\n Latest\n </option>\n <option value=\"recentcommentdesc\">Last Commented</option>\n </select>\n </div>\n </div>\n <div className={\"card p-4\"}>\n <Widget\n key=\"feed\"\n src=\"devhub.megha19.near/widget/devhub.components.feed.SubscribedFeed\"\n props={{\n sort: sort,\n accounts: [\n `discussions.${handle}.community.devhub.near`,\n ],\n }}\n />\n </div>\n </div>\n </MainContent>\n <SidebarContainer>\n <div className=\"d-flex flex-column gap-3\">\n <div className=\"card p-4\">\n <div className=\"mb-2\">{communityData?.description}</div>\n <div className=\"d-flex gap-2 flex-wrap\">\n <Tag>{communityData?.tag} </Tag>\n </div>\n </div>\n <div className=\"card p-4 d-flex flex-column gap-2\">\n <SubHeading>Community Admins</SubHeading>\n {(communityData?.admins ?? []).map((accountId) => (\n <div\n key={accountId}\n className=\"d-flex\"\n style={{ fontWeight: 500 }}\n >\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.ProfileCard\"\n props={{ accountId }}\n />\n </div>\n ))}\n </div>\n </div>\n </SidebarContainer>\n </Container>\n </div>\n);\n" }, "devhub.entity.proposal.Profile": { "": "const accountId = props.accountId;\nconst size = props.size ?? \"md\";\nconst showAccountId = props.showAccountId;\nconst Avatar = styled.div`\n &.sm {\n min-width: 30px;\n max-width: 30px;\n min-height: 30px;\n max-height: 30px;\n }\n &.md {\n min-width: 40px;\n max-width: 40px;\n min-height: 40px;\n max-height: 40px;\n }\n pointer-events: none;\n flex-shrink: 0;\n border: 1px solid #eceef0;\n overflow: hidden;\n border-radius: 40px;\n transition: border-color 200ms;\n\n img {\n object-fit: cover;\n width: 100%;\n height: 100%;\n margin: 0 !important;\n }\n`;\nconst profile = Social.get(`${accountId}/profile/**`, \"final\");\nconst profileUrl = `/near/widget/ProfilePage?accountId=${accountId}`;\nreturn (\n <Link href={profileUrl}>\n <div className=\"d-flex gap-2 align-items-center\">\n <Avatar className={size}>\n <Widget\n src=\"mob.near/widget/Image\"\n props={{\n image: profile.image,\n alt: profile.name,\n fallbackUrl:\n \"https://ipfs.near.social/ipfs/bafkreibiyqabm3kl24gcb2oegb7pmwdi6wwrpui62iwb44l7uomnn3lhbi\",\n }}\n />\n </Avatar>\n {showAccountId && (\n <div>\n {(accountId ?? \"\").substring(0, 20)}\n {(accountId ?? \"\").length > 20 ? \"...\" : \"\"}\n </div>\n )}\n </div>\n </Link>\n);\n" }, "devhub.page.feed": { "": "const { author, recency, tag } = props;\n\nconst { href } = VM.require(\"devhub.megha19.near/widget/core.lib.url\");\n\nif (!href) {\n return <p>Loading modules...</p>;\n}\n\nconst Gradient = styled.div`\n height: 250px;\n text-align: center;\n background: radial-gradient(\n circle,\n rgba(29, 55, 57, 1) 30%,\n rgba(24, 24, 24, 1) 80%\n );\n\n font-family: Arial, sans-serif;\n\n .text-primary-gradient {\n color: #53fdca;\n -webkit-text-fill-color: transparent;\n background-image: linear-gradient(#8e76ba, #1ed2f0);\n -webkit-background-clip: text;\n background-clip: text;\n }\n\n .subtitle-above {\n font-size: 18px;\n letter-spacing: 1px;\n font-family: Courier, monospace;\n }\n\n .subtitle-below {\n font-size: 16px;\n }\n\n .slogan {\n font-weight: 600;\n font-size: 60px;\n }\n`;\n\nconst FeedPage = ({ recency, tag }) => {\n return (\n <div className=\"w-100\">\n <Widget src={`devhub.megha19.near/widget/devhub.components.island.banner`} />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.feature.post-search.panel\"}\n props={{\n hideHeader: false,\n children: (\n <Widget\n src={\n \"devhub.megha19.near/widget/devhub.components.molecule.PostControls\"\n }\n props={{\n title: \"Post\",\n href: href({\n widgetSrc: \"devhub.megha19.near/widget/app\",\n params: { page: \"create\" },\n }),\n }}\n />\n ),\n recency,\n tag,\n author,\n transactionHashes: props.transactionHashes,\n }}\n />\n </div>\n );\n};\n\nreturn FeedPage(props);\n" }, "devhub.entity.proposal.ConfirmReviewModal": { "": "const isOpen = props.isOpen;\nconst onCancelClick = props.onCancelClick;\nconst onReviewClick = props.onReviewClick;\n\nconst Modal = styled.div`\n display: ${({ hidden }) => (hidden ? \"none\" : \"flex\")};\n position: fixed;\n inset: 0;\n justify-content: center;\n align-items: center;\n opacity: 1;\n z-index: 999;\n .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n\n @media screen and (max-width: 768px) {\n h5 {\n font-size: 16px !important;\n }\n }\n\n .btn {\n font-size: 14px;\n }\n`;\n\nconst ModalBackdrop = styled.div`\n position: absolute;\n inset: 0;\n background-color: rgba(0, 0, 0, 0.5);\n opacity: 0.4;\n`;\n\nconst ModalDialog = styled.div`\n padding: 2em;\n z-index: 999;\n overflow-y: auto;\n max-height: 85%;\n margin-top: 5%;\n width: 50%;\n\n @media screen and (max-width: 768px) {\n margin: 2rem;\n width: 100%;\n }\n`;\n\nconst ModalHeader = styled.div`\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding-bottom: 4px;\n`;\n\nconst ModalFooter = styled.div`\n padding-top: 4px;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: items-center;\n`;\n\nconst CloseButton = styled.button`\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: white;\n padding: 0.5em;\n border-radius: 6px;\n border: 0;\n color: #344054;\n\n &:hover {\n background-color: #d3d3d3;\n }\n`;\n\nconst ConfirmButton = styled.button`\n padding: 0.7em;\n border-radius: 6px;\n border: 0;\n box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);\n background-color: #12b76a;\n color: white;\n\n &:hover {\n background-color: #0e9f5d;\n }\n`;\n\nconst ModalContent = styled.div`\n flex: 1;\n font-size: 14px;\n margin-top: 4px;\n margin-bottom: 4px;\n overflow-y: auto;\n max-height: 50%;\n\n @media screen and (max-width: 768px) {\n font-size: 12px !important;\n }\n`;\n\nconst NoButton = styled.button`\n background: transparent;\n border: none;\n padding: 0;\n margin: 0;\n box-shadow: none;\n`;\n\nreturn (\n <>\n <Modal hidden={!isOpen}>\n <ModalBackdrop />\n <ModalDialog className=\"card\">\n <ModalHeader>\n <h5 className=\"mb-0\">Confirm proposal is ready for for review</h5>\n </ModalHeader>\n <ModalContent>\n Once in official review, you can no longer edit the proposal. But\n comments are still open. <br />\n Are you sure you are ready to proceed?\n </ModalContent>\n <div className=\"d-flex gap-2 align-items-center justify-content-end mt-2\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-secondary\" },\n label: \"Cancel\",\n onClick: onCancelClick,\n }}\n />\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"black-btn\" },\n label: \"Ready for review\",\n onClick: onReviewClick,\n }}\n />\n </div>\n </ModalDialog>\n </Modal>\n </>\n);\n" }, "devhub.entity.addon.blog.editor.content": { "": "const { Card } =\n VM.require(\"devhub.megha19.near/widget/devhub.entity.addon.blog.Card\") ||\n (() => <></>);\nconst { Page } =\n VM.require(\"devhub.megha19.near/widget/devhub.entity.addon.blog.Page\") ||\n (() => <></>);\n\nconst categories = [\n {\n label: \"Guide\",\n value: \"guide\",\n },\n {\n label: \"News\",\n value: \"news\",\n },\n {\n label: \"Reference\",\n value: \"reference\",\n },\n];\n\nconst selectOptions = useMemo(\n () =>\n categories.map((it) => ({\n label: it.label,\n value: it.value,\n })),\n [categories]\n);\n\nconst Banner = styled.div`\n border-radius: var(--bs-border-radius-xl) !important;\n height: 100%;\n\n & > div :not(.btn) {\n position: absolute;\n display: none;\n margin: 0 !important;\n width: 0 !important;\n height: 0 !important;\n }\n\n .btn {\n padding: 0.5rem 0.75rem !important;\n min-height: 32;\n line-height: 1;\n\n border: none;\n border-radius: 50px;\n --bs-btn-color: #ffffff;\n --bs-btn-bg: #087990;\n --bs-btn-border-color: #087990;\n --bs-btn-hover-color: #ffffff;\n --bs-btn-hover-bg: #055160;\n --bs-btn-hover-border-color: #055160;\n --bs-btn-focus-shadow-rgb: 49, 132, 253;\n --bs-btn-active-color: #ffffff;\n --bs-btn-active-bg: #055160;\n --bs-btn-active-border-color: #055160;\n --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n opacity: 0.8;\n\n &:hover {\n opacity: 1;\n }\n }\n`;\n\nconst { data, handle, onSubmit } = props;\n\nconst initialData = data;\n\nconst [content, setContent] = useState(initialData.content || \"\");\nconst [title, setTitle] = useState(initialData.title || \"\");\nconst [subtitle, setSubtitle] = useState(initialData.subtitle || \"\");\nconst [description, setDescription] = useState(initialData.description || \"\");\nconst [author, setAuthor] = useState(initialData.author || \"\");\nconst [previewMode, setPreviewMode] = useState(\"card\"); // \"card\" or \"page\"\nconst [date, setDate] = useState(initialData.date || new Date());\nconst [category, setCategory] = useState(\"guide\");\n\nconst Container = styled.div`\n width: 100%;\n margin: 0 auto;\n padding: 20px;\n text-align: left;\n`;\n\nconst hasDataChanged = () => {\n return (\n content !== initialData.content ||\n title !== initialData.title ||\n author !== initialData.author ||\n subtitle !== initialData.subtitle ||\n description !== initialData.description ||\n date !== initialData.date ||\n category !== initialData.category\n );\n};\n\nconst handlePublish = () => {\n onSubmit &&\n onSubmit(\n {\n id: data.id || undefined,\n title,\n subtitle,\n description,\n date,\n content,\n author,\n category,\n community: handle,\n },\n data.id !== undefined\n );\n};\n\nfunction Preview() {\n switch (previewMode) {\n case \"card\": {\n return (\n <Card\n data={{\n title,\n subtitle,\n description,\n date,\n content,\n author,\n category,\n community: handle,\n }}\n />\n );\n }\n case \"page\": {\n return (\n <Page\n data={{\n title,\n subtitle,\n description,\n date,\n content,\n author,\n category,\n community: handle,\n }}\n />\n );\n }\n }\n}\n\nreturn (\n <Container>\n <ul className=\"nav nav-tabs\" id=\"editPreviewTabs\" role=\"tablist\">\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className=\"nav-link active\"\n id=\"edit-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#edit\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"edit\"\n aria-selected=\"true\"\n >\n Edit\n </button>\n </li>\n <li className=\"nav-item\" role=\"presentation\">\n <button\n className=\"nav-link\"\n id=\"preview-tab\"\n data-bs-toggle=\"tab\"\n data-bs-target=\"#preview\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"preview\"\n aria-selected=\"false\"\n >\n Preview\n </button>\n </li>\n </ul>\n <div className=\"tab-content\" id=\"editPreviewTabsContent\">\n <div\n className=\"tab-pane show active p-4\"\n id=\"edit\"\n role=\"tabpanel\"\n aria-labelledby=\"edit-tab\"\n >\n <Widget\n src=\"devhub.megha19.near/widget/devhub.entity.addon.blog.editor.form\"\n props={{\n title,\n setTitle,\n subtitle,\n setSubtitle,\n options: selectOptions,\n category,\n setCategory,\n description,\n setDescription,\n author,\n setAuthor,\n date,\n setDate,\n content,\n setContent,\n }}\n />\n <div\n className={\"d-flex align-items-center justify-content-end gap-3 mt-4\"}\n >\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-success\" },\n disabled: !hasDataChanged(),\n icon: {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: \"Publish\",\n onClick: handlePublish,\n }}\n />\n </div>\n </div>\n <div\n className=\"tab-pane\"\n id=\"preview\"\n role=\"tabpanel\"\n aria-labelledby=\"preview-tab\"\n style={{ position: \"relative\" }}\n >\n <div style={{ position: \"absolute\", top: 10, right: 0, zIndex: 9999 }}>\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Switch\"\n props={{\n currentValue: previewMode,\n key: \"previewMode\",\n onChange: (e) => setPreviewMode(e.target.value),\n options: [\n { label: \"Card\", value: \"card\" },\n { label: \"Page\", value: \"page\" },\n ],\n\n title: \"Preview mode selection\",\n }}\n />\n </div>\n <div className=\"w-100 h-100 p-4\">\n <Preview />\n </div>\n </div>\n </div>\n </Container>\n);\n" }, "devhub.components.island.support": { "": "const Items = [\n {\n heading: (\n <>\n Developer\n <br />\n Resources\n </>\n ),\n description: \"Learn the fundamentals of NEAR and build with confidence\",\n cta: {\n href: \"https://docs.near.org\",\n title: \"Read ↗\",\n },\n },\n {\n heading: <>Office Hours</>,\n description: (\n <>\n Need some help?\n <br /> DevRel contributors are available to answer your technical\n questions\n </>\n ),\n cta: {\n href: \"/devhub.near/widget/app?page=community&handle=devrel&tab=office-hours-support\",\n title: \"Join ↗\",\n },\n },\n {\n heading: <>Get Funding</>,\n description:\n \"Explore funding opportunities from DevHub to fuel your vision\",\n cta: {\n href: \"/devhub.near/widget/app?page=community&handle=developer-dao&tab=funding\",\n title: \"Learn more ↗\",\n },\n },\n];\n\nconst Circle = styled.div`\n display: flex;\n width: 18.75rem;\n height: 18.75rem;\n padding: 2.25rem;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n gap: 1rem;\n flex-shrink: 0;\n\n border-radius: 22.5rem;\n border: 1px solid #00ec97;\n background: #f4f4f4;\n\n h3 {\n color: #101820;\n text-align: center;\n font-size: 1.75rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 36px */\n }\n\n p {\n color: #101820;\n text-align: center;\n font-size: 1.125rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.72px;\n }\n\n a {\n color: #555555;\n font-size: 1.125rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n }\n`;\n\nconst Container = styled.div`\n padding: 3rem;\n padding-top: 0;\n margin-top: 1.5rem;\n\n @media screen and (max-width: 786px) {\n padding: 1.5rem;\n padding-top: 0;\n }\n`;\n\nconst ItemsContainer = styled.div`\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n width: 100%;\n\n flex-wrap: wrap;\n gap: 3rem;\n\n @media screen and (max-width: 768px) {\n flex-direction: column;\n gap: 1rem;\n }\n`;\n\nconst Content = (\n <Container>\n <ItemsContainer>\n {Items.map((it) => (\n <Circle key={Math.random()}>\n <h3>{it.heading}</h3>\n <p>{it.description}</p>\n <a href={it.cta.href}>{it.cta.title}</a>\n </Circle>\n ))}\n </ItemsContainer>\n </Container>\n);\n\nreturn (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.island.home-section\"\n props={{\n title: \"/get support\",\n children: Content,\n background: true,\n }}\n />\n);\n" }, "devhub.components.molecule.ListEditor": { "": "const { data, setList, validate, invalidate } = props;\n\nconst [newItem, setNewItem] = useState(\"\");\n\nconst handleAddItem = () => {\n if (validate(newItem)) {\n setList([...data.list, newItem]);\n setNewItem(\"\");\n } else {\n return invalidate();\n }\n};\n\nconst handleDeleteItem = (index) => {\n const updatedData = [...data.list];\n updatedData.splice(index, 1);\n setList(updatedData);\n};\n\nconst Item = styled.div`\n padding: 10px;\n margin: 5px;\n display: flex;\n align-items: center;\n flex-direction: row;\n gap: 10px;\n`;\n\nreturn (\n <>\n {data.list.map((item, index) => (\n <Item key={index}>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: item,\n skipPaddingGap: true,\n placeholder: data.placeholder,\n inputProps: {\n prefix: data.prefix,\n disabled: true,\n },\n }}\n />\n </div>\n <button\n className=\"btn btn-outline-danger\"\n onClick={() => handleDeleteItem(index)}\n >\n <i className=\"bi bi-trash-fill\" />\n </button>\n </Item>\n ))}\n {data.list.length < data.maxLength && (\n <Item>\n <div className=\"flex-grow-1\">\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n skipPaddingGap: true,\n onChange: (e) => setNewItem(e.target.value),\n value: newItem,\n placeholder: data.placeholder,\n inputProps: {\n prefix: data.prefix,\n },\n key: data.prefix + \"new-list-item\",\n }}\n />\n </div>\n <button\n className=\"btn btn-success add-member\"\n onClick={handleAddItem}\n disabled={newItem === \"\"}\n data-testid=\"add-to-list\"\n >\n <i className=\"bi bi-plus\" />\n </button>\n </Item>\n )}\n </>\n);\n" }, "devhub.notification.Item": { "": "const { value } = props;\n\nreturn (\n <Widget\n src=\"devhub.near/widget/devhub.notification.LR\"\n props={{\n L: (\n <Widget\n src=\"devhub.near/widget/devhub.notification.Left\"\n props={{ type: value.type, proposal: value.proposal }}\n />\n ),\n R: (\n <Widget\n src=\"devhub.near/widget/DevGov.notification.Right\"\n props={{ proposal: value.proposal }}\n />\n ),\n ...props,\n }}\n />\n);\n" }, "devhub.components.atom.Toggle": { "": "const ToggleRoot = styled.div`\n justify-content: space-between;\n width: fit-content;\n max-width: 100%;\n`;\n\nconst ToggleSwitchRoot = styled(\"Switch.Root\")`\n all: unset;\n display: block;\n width: 42px;\n height: 25px;\n background-color: #d1d1d1;\n border-radius: 9999px;\n position: relative;\n box-shadow: 0 2px 10px var(--blackA7);\n\n &[data-state=\"checked\"] {\n background-color: #00d084;\n }\n\n &[data-disabled=\"\"] {\n opacity: 0.7;\n }\n`;\n\nconst ToggleSwitchThumb = styled(\"Switch.Thumb\")`\n all: unset;\n display: block;\n width: 21px;\n height: 21px;\n border-radius: 9999px;\n transition: transform 100ms;\n transform: translateX(2px);\n will-change: transform;\n\n &[data-state=\"checked\"] {\n transform: translateX(19px);\n }\n`;\n\nconst ToggleLabel = styled.label`\n white-space: nowrap;\n`;\n\nconst Toggle = ({\n className,\n direction,\n disabled,\n inputProps,\n key,\n label,\n onChange,\n value: checked,\n ...rest\n}) => (\n <ToggleRoot\n className={[\n \"d-flex justify-content-between, align-items-center gap-3 p-2\",\n direction === \"rtl\" ? \"flex-row-reverse\" : \"\",\n className,\n ].join(\" \")}\n {...rest}\n >\n <ToggleLabel htmlFor={`toggle-${key}`}>{label}</ToggleLabel>\n\n <ToggleSwitchRoot\n className=\"shadow-none\"\n id={`toggle-${key}`}\n onCheckedChange={disabled ? null : onChange}\n {...{ checked, disabled, ...inputProps }}\n >\n {!disabled && <ToggleSwitchThumb className=\"bg-light shadow\" />}\n </ToggleSwitchRoot>\n </ToggleRoot>\n);\n\nreturn Toggle(props);\n" }, "devhub.entity.proposal.LoginScreen": { "": "const Container = styled.div`\n height: 60vh;\n .card-custom {\n box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);\n border-radius: 5px;\n background-color: white;\n }\n\n @media screen and (max-width: 768px) {\n .card-custom {\n margin: 2rem;\n }\n }\n\n .bg-grey {\n background-color: #f3f3f2;\n }\n\n .cursor {\n cursor: pointer;\n }\n\n .black-btn {\n background-color: #000 !important;\n border: none;\n color: white;\n &:active {\n color: white;\n }\n }\n`;\n\nreturn (\n <Container className=\"d-flex justify-content-center align-items-center w-100\">\n <div class=\"card-custom\">\n <div class=\"card-body p-4 d-flex flex-column gap-2 justify-content-center align-items-center\">\n <i class=\"bi bi-person-circle h2\"></i>\n <div className=\"h5\">Not Logged in</div>\n <p className=\"text-muted text-center\">\n You must log in to create or interact with proposals.\n </p>\n <div className=\"d-flex gap-2 align-items-center justify-content-end mt-2\">\n <Link to=\"https://near.org/signin\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-secondary\" },\n label: \"Sign In\",\n }}\n />\n </Link>\n <Link to=\"https://near.org/signup\">\n <Widget\n src={\"devhub.megha19.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"black-btn\" },\n label: \"Create Account\",\n }}\n />\n </Link>\n </div>\n </div>\n </div>\n </Container>\n);\n" }, "devhub.components.island.explore": { "": "const Card = styled.div`\n display: flex;\n max-width: 20rem;\n max-height: 17.5rem;\n padding: 1.5rem;\n flex-direction: column;\n justify-content: space-between;\n align-items: center;\n\n background: #fff;\n border-radius: 1rem;\n\n h3 {\n color: #555555; //#00ec97;\n text-align: center;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n }\n\n p {\n color: #555;\n text-align: center;\n font-size: 1rem;\n font-style: normal;\n font-weight: 400;\n line-height: 120%; /* 24px */\n }\n\n a {\n color: #555555; //#00ec97;\n text-align: center;\n font-size: 1.25rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 24px */\n }\n\n @media screen and (max-width: 768px) {\n h3 {\n font-size: 1.5rem;\n }\n\n p,\n a {\n font-size: 1rem;\n }\n\n padding: 1rem;\n }\n`;\n\nconst SectionCard = ({ title, description, href }) => {\n return (\n <Card>\n <h3>{title}</h3>\n <p>{description}</p>\n <a href={href}>Learn more →</a>\n </Card>\n );\n};\n\nconst Cards = [\n {\n title: \"217 Validators\",\n description:\n \"to ensure security, liveness, and fault tolerance of the network\",\n href: \"https://nearscope.net/\",\n },\n {\n title: \"<1s, <1¢\",\n description:\n \"Fast and cost-efficient transactions. 1s to update values with minimal fees\",\n href: \"https://nearblocks.io/\",\n },\n {\n title: \"Awesome DevEx\",\n description:\n \"NEAR lets developers innovate with familiar tools: TypeScript, Rust, Solidity\",\n href: \"https://docs.near.org/\",\n },\n {\n title: \"Horizontal Scaling\",\n description:\n \"Nightshade ensures maximum performance thanks to its sharded design\",\n href: \"https://docs.near.org/concepts/advanced/papers\",\n },\n];\n\nconst CTA = styled.a`\n display: flex;\n padding: 0.875rem 1rem;\n align-items: center;\n gap: 0.5rem;\n\n border-radius: 1rem;\n background: #00ec97;\n\n color: #f4f4f4 !important;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 28.8px */\n letter-spacing: -0.03rem;\n\n width: max-content;\n text-decoration: none;\n\n &:hover {\n text-decoration: none;\n background: #555555;\n }\n\n @media screen and (max-width: 768px) {\n color: #f4f4f4 !important;\n font-size: 1.125rem;\n font-style: normal;\n font-weight: 700;\n line-height: 120%; /* 24px */\n letter-spacing: -0.4px;\n\n margin-left: auto;\n margin-right: auto;\n\n display: flex;\n padding: 14px 16px;\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n gap: 8px;\n\n border-radius: 16px;\n background: #00ec97;\n }\n`;\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n gap: 3rem;\n\n padding: 3rem;\n padding-top: 0;\n\n @media screen and (max-width: 768px) {\n padding: 1.5rem;\n padding-top: 0;\n }\n`;\n\nconst CardsContainer = styled.div`\n display: flex;\n justify-content: space-between;\n gap: 1.5rem;\n\n @media screen and (max-width: 768px) {\n display: grid;\n grid-template-columns: 1fr 1fr;\n /* gap: 2.25rem; */\n }\n`;\n\nconst Content = (\n <Container>\n <CardsContainer>\n {Cards.map((card) => (\n <SectionCard\n title={card.title}\n description={card.description}\n href={card.href}\n key={Math.random()}\n />\n ))}\n </CardsContainer>\n <CTA href=\"https://near.org/ecosystem\" target=\"_blank\">\n Explore the Open Web on NEAR →\n </CTA>\n </Container>\n);\n\nreturn (\n <Widget\n src=\"devhub.megha19.near/widget/devhub.components.island.home-section\"\n props={{\n title: \"/explore\",\n titleColor: \"#555555\",\n description:\n \"NEAR Protocol is your fast, low-cost and reliable gateway to the Open Web\",\n children: Content,\n }}\n />\n);\n" } } } } }
Failure:
{
  "type": "action",
  "error": {
    "type": "functionCallError",
    "error": {
      "type": "executionError",
      "error": "Smart contract panicked: panicked at 'Not enough storage balance', contract/src/account.rs:80:9"
    }
  }
}
No logs
Receipt:
Predecessor ID:
Gas Burned:
223 Ggas
Tokens Burned:
0 
Transferred 3.12  to devhub.megha19.near
Empty result
No logs
Receipt:
Predecessor ID:
Gas Burned:
223 Ggas
Tokens Burned:
0 
Transferred 0.16285  to devhub.megha19.near
Empty result
No logs