{
"data": {
"chatter.cheddar.near": {
"widget": {
"Cheddar.ArticleView": {
"": "// Cheddar.ArticleView\nconst { getComments } = VM.require(\n 'chatter.cheddar.near/widget/lib.comment'\n) || { getComments: () => {} }\nconst { getConfig } = VM.require(\n 'chatter.cheddar.near/widget/config.CommunityVoice'\n) || { getConfig: () => {} }\nconst { getUpVotes } = VM.require(\n 'chatter.cheddar.near/widget/lib.upVotes'\n) || { getUpVotes: () => {} }\nconst { getArticlesVersions } = VM.require(\n 'chatter.cheddar.near/widget/lib.article'\n) || { getArticlesVersions: () => {} }\n\nconst { HumanityWrapperButton, isHuman } = VM.require(\n 'chatter.cheddar.near/widget/lib.nadaBot'\n) || { HumanityWrapperButton: () => <></>, isHuman: () => {} }\n\nconst {\n widgets,\n isTest,\n handleFilterArticles,\n articleToRenderData,\n authorForWidget,\n handleEditArticle,\n handleDeleteArticle,\n handleShareButton,\n baseActions,\n kanbanColumns,\n sharedData,\n} = props\n\nconst accountId = articleToRenderData.value.metadata.author\nconst id =\n articleToRenderData.value.metadata.id ??\n `article/${articleToRenderData.value.metadata.author}/${articleToRenderData.value.metadata.createdTimestamp}`\n\nif (\n !Array.isArray(articleToRenderData.value.articleData.tags) &&\n typeof articleToRenderData.value.articleData.tags === 'object'\n) {\n articleToRenderData.value.articleData.tags = Object.keys(\n articleToRenderData.value.articleData.tags\n )\n}\n\narticleToRenderData.value.articleData.tags =\n articleToRenderData.value.articleData.tags.filter(\n (tag) => tag !== undefined && tag !== null\n )\n\nconst tabs = [\n {\n id: 'generalInfo',\n title: 'Post info',\n icon: 'bi bi-primary-circle',\n },\n]\n\n//To slice the article body and show the showMore button just uncoment the sliceContent: true, un the State.init\nState.init({\n tabSelected: tabs[0].id,\n // sliceContent: true,\n})\n\nconst [comments, setComments] = useState(undefined)\nconst [loadingComments, setLoadingComments] = useState(true)\nconst [versions, setVersions] = useState([])\n\nif (versions.length === 0) {\n try {\n const versionsPromise = getArticlesVersions(\n getConfig(isTest),\n articleToRenderData.value.metadata.id\n )\n versionsPromise.then((newVersions) => {\n setVersions(newVersions)\n })\n } catch (err) {\n return console.error('Error in article history handler: ', err)\n }\n}\n\nfunction loadComments() {\n const articleId = articleToRenderData.value.metadata.id\n getComments(articleId, getConfig(isTest)).then((newComments) => {\n setComments(newComments)\n setLoadingComments(false)\n })\n}\n\nuseEffect(() => {\n loadComments()\n setInterval(() => {\n loadComments()\n }, 30000)\n}, [])\n\nconst [upVotes, setUpVotes] = useState(undefined)\nconst [loadingUpVotes, setLoadingUpVotes] = useState(true)\n\nfunction loadUpVotes() {\n getUpVotes(getConfig(isTest), id).then((newVotes) => {\n setUpVotes(newVotes)\n setLoadingUpVotes(false)\n })\n}\n\nuseEffect(() => {\n loadUpVotes()\n setInterval(() => {\n loadUpVotes()\n }, 30000)\n}, [])\n\nconst timeLastEdit = new Date(\n articleToRenderData.value.metadata.lastEditTimestamp\n)\n\nconst CursorPointer = styled.div`\n margin-bottom: 0.5rem;\n cursor: pointer;\n`\n\nconst DetailContent = styled.div`\n display: inline-flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n`\n\nconst TagContainer = styled.div`\n display: flex;\n flex-wrap: wrap;\n align-items: flex-start;\n gap: 4px;\n margin-top: 1rem;\n`\n\nconst HouseTagDiv = styled.div`\n display: flex;\n padding: 4px 8px;\n justify-content: center;\n align-items: center;\n gap: 10px;\n border-radius: 100px;\n background: var(\n --gradient-purple-gradient,\n linear-gradient(90deg, #9333ea 0%, #4f46e5 100%)\n );\n`\n\nconst HouseTagText = styled.p`\n color: #fff;\n font-size: 7px;\n font-weight: 500;\n line-height: 120%;\n margin: 0px;\n`\n\nconst TagDiv = styled.div`\n display: flex;\n justify-content: center;\n padding: 4px 8px;\n align-items: center;\n gap: 10px;\n border-radius: 100px;\n border: solid 1px transparent;\n border-radius: 80px;\n background-image: linear-gradient(#eae5f7, #eae5f7),\n radial-gradient(circle at top left, #9333ea 0%, #4f46e5 100%);\n background-origin: border-box;\n background-clip: padding-box, border-box;\n`\n\nconst TagDivText = styled.p`\n font-size: 8px;\n margin: 0px;\n font-weight: 500;\n line-height: 120%;\n background: linear-gradient(90deg, #9333ea 0%, #4f46e5 100%);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n background-clip: text;\n text-fill-color: transparent;\n`\n\nconst NominationTitleContainer = styled.div`\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n`\n\nconst NominationTitle = styled.p`\n display: flex;\n flex-direction: column;\n justify-content: center;\n margin: 7px 0 0 0;\n color: #000;\n font-size: 18px;\n font-weight: 500;\n line-height: 120%;\n`\nconst UserLink = styled.a`\n cursor: pointer;\n &:hover {\n text-decoration: none;\n }\n`\nconst NominationUser = styled.p`\n display: flex;\n flex-direction: column;\n justify-content: center;\n color: #828688;\n margin: 0 0 7px 0;\n font-size: 14px;\n line-height: 120%;\n`\n\nconst UpvoteButtonDisabled = styled.button`\n display: flex;\n padding: 2px 12px;\n align-items: center;\n gap: 6px;\n border-radius: 4px;\n border: solid 1px transparent;\n background: var(--buttons-disable, #c3cace);\n cursor: default !important;\n`\n\nconst UpvoteButton = styled.button`\n display: flex;\n padding: 2px 12px;\n align-items: center;\n gap: 6px;\n border-radius: 4px;\n border: solid 1px transparent;\n background-image: linear-gradient(#f8f8f9, #f8f8f9),\n radial-gradient(\n circle at left top,\n rgb(147, 51, 234) 0%,\n rgb(79, 70, 229) 100%\n );\n background-origin: border-box;\n background-clip: padding-box, border-box;\n`\n\nconst UpvoteCount = styled.p`\n font-size: 12px;\n font-weight: 500;\n line-height: 24px;\n margin: 0px;\n background: linear-gradient(90deg, #9333ea 0%, #4f46e5 100%);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n background-clip: text;\n text-fill-color: transparent;\n`\n\nconst Icon = styled.img`\n width: 17px;\n height: 17px;\n`\nconst BodyContainer = styled.div`\n border-radius: 8px;\n margin: 10px 0;\n background: #f8f8f9;\n padding: 20px;\n`\n\nconst PlatformCard = styled.div`\n display: flex;\n border-radius: 6px;\n background: background: \"rgb(255 255 255 / 0%);\n `\n\nconst PlatformContent = styled.div`\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n width: 100%;\n`\n\nconst PlatformInfoDiv = styled.div`\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n`\n\nconst PlatformInfoHeader = styled.div`\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 2px;\n`\n\nconst PlatInforHeadText = styled.p`\n margin: 0px;\n color: var(--000000, #000);\n font-size: 10px;\n font-weight: 500;\n line-height: 120%;\n`\n\nconst PlatInfoHeadSeparator = styled.hr`\n height: 0px;\n margin: 8px 0 0 0;\n\n border: 1px solid rgba(208, 214, 217, 1);\n`\n\nconst KeyIssuesContainer = styled.div`\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 2px;\n`\n\nconst KeyIssueTitle = styled.p`\n font-size: 12px;\n line-height: 120%;\n margin: 0px;\n font-weight: 500;\n line-height: 18px;\n text-align: left;\n padding: 10px;\n`\n\nconst KeyIssueDescription = styled.p`\n color: #212427;\n font-size: 12px;\n line-height: 130%;\n margin: 0px;\n padding: 10px;\n line-height: 18px;\n text-align: justify;\n`\n\nconst CandidateCard = styled.div`\n display: flex;\n padding: 20px;\n align-items: center;\n align-self: stretch;\n border-radius: 6px;\n background: #fff;\n`\n\nconst CandidateContent = styled.div`\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n gap: 12px;\n`\n\nconst ContentHeader = styled.div`\n display: flex;\n align-items: center;\n gap: 12px;\n align-self: stretch;\n`\n\nconst ContentHeaderText = styled.p`\n font-size: 18px;\n font-weight: 500;\n margin: 0px;\n`\n\nconst CandidateInfoDiv = styled.div`\n width: 100%;\n padding: 16px;\n background: white;\n gap: 16px;\n border-radius: 8px;\n`\n\nconst CandidateInfoHeader = styled.div`\n display: flex;\n align-items: center;\n gap: 8px;\n align-self: stretch;\n`\n\nconst CandidateImage = styled.img`\n width: 32px;\n height: 32px;\n`\n\nconst CandidateInfoData = styled.div`\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n flex: 1 0 0;\n`\n\nconst CandidateTagDiv = styled.div`\n display: flex;\n height: 20px;\n padding: 4px 8px;\n justify-content: center;\n align-items: center;\n gap: 10px;\n border-radius: 100px;\n border: 1px solid var(--secondary-warning, #f19d38);\n background: #f0e1ce;\n`\n\nconst CandidateTagText = styled.p`\n color: var(--secondary-warning, #f19d38);\n font-size: 10px;\n font-weight: 500;\n line-height: 120%;\n margin: 0px;\n`\n\nconst CandidateTime = styled.h6`\n margin: 3px 0 0 0;\n font-size: 10px;\n font-weight: 500;\n line-height: 120%;\n color: #828688;\n`\n\nconst CandidateTextInfo = styled.div`\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n align-self: stretch;\n`\n\nconst SectionTitle = styled.h5`\n font-size: 12px;\n font-weight: 500;\n line-height: 120%;\n margin: 16px 0 0 0;\n`\n\nconst SectionDescription = styled.p`\n font-size: 12px;\n line-height: 18px;\n margin: 0px;\n text-align: justify;\n color: #828688;\n`\n\nconst DescriptionSubtitle = styled.h5`\n display: inline-block;\n font-size: 12px;\n line-height: 120%;\n margin-right: 0.3rem;\n`\n\nconst DescriptionInfoSpan = styled.span`\n font-size: 12px;\n line-height: 18px;\n margin: 0px;\n text-align: justify;\n color: #828688;\n`\n\nconst DeclarationCard = styled.div`\n padding: 0px;\n`\n\nconst CommentSection = styled.div`\n display: flex;\n padding: 20px;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n border-radius: 8px;\n background: #f8f8f9;\n`\n\nconst Container = styled.div`\n display: flex;\n margin-right: 5px;\n justify-content: center;\n`\n\nconst SecondContainer = styled.div`\n background: #f8f8f9;\n border-radius: 8px;\n padding: 20px;\n`\n\nconst H6 = styled.h6`\n font-size: 14px;\n margin-bottom: 0;\n`\n\nconst Tab = styled.div`\n font-weight: ${(props) => (props.active ? '600' : '500')};\n border-bottom: 2px solid;\n border-color: ${(props) => (props.active ? '#ffd50d' : '#dee2e6')};\n color: ${(props) => (props.active ? '#ffd50d' : '#ababab')};\n cursor: pointer;\n padding-bottom: 8px;\n font-size: 14px;\n\n i {\n &::before {\n color: ${(props) => (props.active ? '#ffd50d' : '#ababab')};\n }\n margin-right: 5px;\n }\n`\n\nconst TH = styled.th`\n border: 1px solid rgba(208, 214, 217, 0.4) !important;\n text-align: left !important;\n padding: 15px 20px !important;\n`\n\nconst CallLibrary = styled.div`\n display: none;\n`\n\nconst HeaderButtonsContainer = styled.div`\n display: flex;\n justify-content: flex-end;\n gap: 0.5rem;\n`\n\nconst EditDeleteButtonsContainer = styled.div`\n display: flex;\n gap: 0.5rem;\n`\n\nconst AcordionContainer = styled.div`\n --bs-accordion-border-width: 0px !important;\n`\n\nconst NoMargin = styled.div`\n margin: 0 0.75rem;\n`\n\nconst AccordionBody = styled.div`\n padding: 0;\n`\n\n//Get basic original comments info\nconst rootComments = comments\n ? comments.filter((comment) => comment.value.metadata.rootId === id)\n : []\n\n//Append answers to original comments\nconst articleComments = rootComments.map((rootComment) => {\n let answers = comments.filter((comment) => {\n return comment.value.metadata.rootId === rootComment.value.metadata.id\n })\n\n return {\n ...rootComment,\n answers,\n }\n})\n\nfunction stateUpdate(obj) {\n State.update(obj)\n}\n\nfunction getUserName() {\n const profile = data.authorProfile\n\n return profile.name ?? getShortUserName()\n}\n\nconst getShortUserName = () => {\n const userId = accountId\n\n if (userId.length === 64)\n return `${userId.slice(0, 4)}..${userId.slice(-4)}`\n const name = userId.slice(0, -5) // truncate .near\n\n return name.length > 20 ? `${name.slice(0, 20)}...` : name\n}\n\nlet displayedContent = state.sliceContent\n ? articleToRenderData.value.articleData.body.slice(0, 1000)\n : articleToRenderData.value.articleData.body\n\nreturn (\n <>\n {sharedData.sharedCommentId && (\n <a href={`#${sharedData.sharedCommentId}`}>\n Click to redirect to comment that mentioned you\n </a>\n )}\n\n <AcordionContainer className=\"accordion\" id=\"accordionExample\">\n <NoMargin className=\"accordion-item\">\n <h2 className=\"accordion-header\" id=\"headingOne\">\n <button\n className=\"accordion-button collapsed border border-2\"\n type=\"button\"\n data-bs-toggle=\"collapse\"\n data-bs-target=\"#collapseOne\"\n aria-expanded=\"true\"\n aria-controls=\"collapseOne\"\n >\n <h6 className=\"m-0\">Show article history</h6>\n </button>\n </h2>\n <div\n id=\"collapseOne\"\n className=\"accordion-collapse collapse\"\n aria-labelledby=\"headingOne\"\n data-bs-parent=\"#accordionExample\"\n >\n <AccordionBody className=\"accordion-body\">\n <div className=\"ps-5\">\n <Widget\n src={\n widgets.views.editableWidgets.articleHistory\n }\n props={{\n articleId:\n articleToRenderData.value.metadata.id,\n isTest,\n baseActions,\n kanbanColumns,\n widgets,\n versions,\n }}\n />\n </div>\n </AccordionBody>\n </div>\n </NoMargin>\n </AcordionContainer>\n\n <Container className=\"mt-1 row\">\n <div className=\"col-lg-9 col-sm-12\">\n <div className=\"row\" style={{ 'margin-inline': '5px' }}>\n <div\n className=\"col-12 p-0 w-100\"\n style={{\n background: '#F8F8F9',\n 'border-radius': '8px',\n }}\n >\n <div className=\"w-100 p-3 d-flex flex-wrap justify-content-between align-items-start\">\n <div className=\"d-flex flex-column w-75\">\n <Widget\n src={\n widgets.views.standardWidgets\n .newStyledComponents.Element.User\n }\n props={{\n accountId,\n options: {\n showHumanBadge: true,\n showImage: true,\n showSocialName: true,\n shortenLength: 20,\n size: 'lg',\n },\n }}\n />\n <TagContainer>\n {articleToRenderData.value.articleData.tags\n .length > 0 &&\n articleToRenderData.value.articleData.tags.map(\n (tag) => {\n const filter = {\n filterBy: 'tag',\n value: tag,\n }\n return (\n <CursorPointer\n onClick={() =>\n handleFilterArticles(\n filter\n )\n }\n >\n <Widget\n src={\n widgets.views\n .standardWidgets\n .newStyledComponents\n .Element\n .Badge\n }\n props={{\n children: tag,\n variant:\n 'round primary outline',\n size: 'lg',\n }}\n />\n </CursorPointer>\n )\n }\n )}\n </TagContainer>\n </div>\n <div className=\"d-flex gap-3\">\n <div className=\"d-flex flex-column\">\n <HeaderButtonsContainer>\n <HumanityWrapperButton\n style={{ all: 'unset' }}\n accountId={context.accountId}\n >\n <Widget\n src={\n widgets.views\n .editableWidgets\n .upVoteButton\n }\n props={{\n isTest,\n authorForWidget,\n reactedElementData:\n articleToRenderData,\n widgets,\n disabled:\n !context.accountId ||\n !isHuman(\n context.accountId\n ),\n upVotes,\n baseActions,\n loadUpVotes,\n loadingUpVotes,\n setLoadingUpVotes,\n }}\n />\n </HumanityWrapperButton>\n <Widget\n src={\n widgets.views.standardWidgets\n .newStyledComponents.Input\n .Button\n }\n props={{\n size: 'sm',\n className:\n 'primary outline icon',\n children: (\n <i className=\"bi bi-share\"></i>\n ),\n onClick: () =>\n handleShareButton(true, {\n key: 'said',\n type: 'sharedArticleId',\n value: articleToRenderData\n .value.metadata.id,\n }),\n }}\n />\n </HeaderButtonsContainer>\n <HumanityWrapperButton\n style={{ all: 'unset' }}\n accountId={context.accountId}\n >\n <Widget\n src={\n widgets.views.editableWidgets\n .reactions\n }\n props={{\n widgets,\n isTest,\n authorForWidget,\n elementReactedId: id,\n disabled:\n !context.accountId ||\n !isHuman(context.accountId),\n baseActions,\n }}\n />\n </HumanityWrapperButton>\n {context.accountId == accountId && (\n <EditDeleteButtonsContainer>\n <Widget\n src={\n widgets.views\n .standardWidgets\n .newStyledComponents\n .Input.Button\n }\n props={{\n children: (\n <div className=\"d-flex justify-content-center align-items-center\">\n <span className=\"mx-2\">\n Edit\n </span>\n <i className=\"bi bi-pencil\"></i>\n </div>\n ),\n className: `primary outline mt-2`,\n onClick: () => {\n handleEditArticle(\n articleToRenderData\n )\n },\n }}\n />\n <Widget\n src={\n widgets.views\n .standardWidgets\n .newStyledComponents\n .Input.Button\n }\n props={{\n children: (\n <div className=\"d-flex justify-content-center align-items-center\">\n <i className=\"bi bi-trash\"></i>\n </div>\n ),\n className: `danger outline mt-2`,\n onClick: () =>\n handleDeleteArticle(\n articleToRenderData\n ),\n }}\n />\n </EditDeleteButtonsContainer>\n )}\n </div>\n </div>\n </div>\n </div>\n <BodyContainer className=\"col-12\">\n <PlatformCard>\n <PlatformContent>\n <ContentHeader>\n <ContentHeaderText>\n {\n articleToRenderData.value\n .articleData.title\n }\n </ContentHeaderText>\n </ContentHeader>\n\n <Widget\n src={\n widgets.views.standardWidgets\n .socialMarkdown\n }\n props={{\n text: displayedContent,\n onHashtag: (hashtag) => (\n <span\n key={hashtag}\n className=\"d-inline-flex\"\n style={{ fontWeight: 500 }}\n >\n <a\n href={`https://near.org/${authorForWidget}/widget/${widgets.thisForum}?st=${hashtag}`}\n target=\"_blank\"\n >\n #{hashtag}\n </a>\n </span>\n ),\n }}\n />\n {state.sliceContent &&\n articleToRenderData.value.articleData.body\n .length > 1000 && (\n <Widget\n src={\n widgets.views.standardWidgets\n .newStyledComponents.Input\n .Button\n }\n props={{\n children: (\n <div className=\"d-flex justify-content-center align-items-center\">\n <span className=\"mx-2\">\n Show more\n </span>\n <i className=\"bi bi-chat-square-text-fill\"></i>\n </div>\n ),\n size: 'sm',\n className: 'w-100',\n onClick: () => {\n State.update({\n sliceContent: false,\n })\n },\n }}\n />\n )}\n </PlatformContent>\n </PlatformCard>\n </BodyContainer>\n <CommentSection>\n <NominationTitle>\n <span>\n <i className=\"bi bi-chat-square-dots-fill\" />{' '}\n Comments\n </span>\n </NominationTitle>\n\n {state.showModal && (\n <Widget\n src={widgets.views.editableWidgets.addComment}\n props={{\n article: articleToRenderData,\n widgets,\n isTest,\n isReplying: false,\n username: accountId,\n onCloseModal: () =>\n State.update({ showModal: false }),\n baseActions,\n loadComments,\n setLoadingComments,\n }}\n />\n )}\n <HumanityWrapperButton\n style={{ all: 'unset' }}\n accountId={context.accountId}\n >\n <Widget\n src={\n widgets.views.standardWidgets\n .newStyledComponents.Input.Button\n }\n props={{\n children: (\n <div className=\"d-flex align-items-center justify-content-cente\">\n <span className=\"mx-1\">\n Add comment\n </span>\n <i className=\"bi bi-plus-lg\"></i>\n </div>\n ),\n disabled: !context.accountId,\n className:\n 'primary outline w-100 mt-4 mb-2',\n onClick: () => {\n State.update({ showModal: true })\n },\n }}\n />\n </HumanityWrapperButton>\n {loadingComments ? (\n <Widget\n src={\n widgets.views.standardWidgets\n .newStyledComponents.Feedback.Spinner\n }\n />\n ) : (\n articleComments.map((data) => (\n <Widget\n src={\n widgets.views.editableWidgets\n .commentView\n }\n props={{\n widgets,\n data,\n isTest,\n authorForWidget,\n isReply: false,\n articleSbts,\n baseActions,\n sharedCommentId,\n articleToRenderData,\n loadComments,\n setLoadingComments,\n }}\n />\n ))\n )}\n </CommentSection>\n </div>\n </div>\n <SecondContainer className=\"col-lg-3 col-sm-12\">\n <>\n <ul className=\"nav nav-pills nav-fill\">\n {tabs.map(({ id, title, icon }, i) => (\n <li\n className=\"nav-item\"\n role=\"presentation\"\n key={i}\n >\n <Tab\n active={state.tabSelected === id}\n onClick={() =>\n State.update({ tabSelected: id })\n }\n >\n <i className={`${icon}`} />\n {title}\n </Tab>\n </li>\n ))}\n </ul>\n <div>\n {state.tabSelected == 'generalInfo' && (\n <DeclarationCard>\n <SectionTitle className=\"mt-4 mb-3\"></SectionTitle>\n <div>\n <DescriptionSubtitle>\n Created by:\n </DescriptionSubtitle>\n <DescriptionInfoSpan>\n {articleToRenderData.authorProfile\n .name ?? getShortUserName()}\n </DescriptionInfoSpan>\n </div>\n <div>\n <DescriptionSubtitle>\n Edited on:\n </DescriptionSubtitle>\n <DescriptionInfoSpan>\n {timeLastEdit + ''}\n </DescriptionInfoSpan>\n </div>\n <div>\n <DescriptionSubtitle>\n Edit versions:\n </DescriptionSubtitle>\n <DescriptionInfoSpan>\n {versions.length ?? 0}\n </DescriptionInfoSpan>\n </div>\n </DeclarationCard>\n )}\n </div>\n </>\n </SecondContainer>\n </Container>\n </>\n)\n"
}
}
}
}
}