diff --git a/instances/devhub.near/aliases.mainnet.json b/instances/devhub.near/aliases.mainnet.json index a877fe047..e153b8a83 100644 --- a/instances/devhub.near/aliases.mainnet.json +++ b/instances/devhub.near/aliases.mainnet.json @@ -10,6 +10,5 @@ "REPL_RPC_URL": "https://rpc.mainnet.near.org", "REPL_PROPOSAL_FEED_INDEXER_QUERY_NAME": "polyprogrammist_near_devhub_prod_v1_proposals_with_latest_snapshot", "REPL_INDEXER_HASURA_ROLE": "polyprogrammist_near", - "REPL_POSTHOG_API_KEY": "01234567890123456789012345678901234567890123456", - "REPL_CACHE_URL": "https://devhub-cache-api-rs.fly.dev" + "REPL_POSTHOG_API_KEY": "01234567890123456789012345678901234567890123456" } diff --git a/instances/devhub.near/widget/app.jsx b/instances/devhub.near/widget/app.jsx index 42f9044c6..221dce7de 100644 --- a/instances/devhub.near/widget/app.jsx +++ b/instances/devhub.near/widget/app.jsx @@ -154,7 +154,7 @@ function Page() { return ( ); } @@ -163,7 +163,7 @@ function Page() { return ( ); } @@ -171,7 +171,7 @@ function Page() { return ( ); } diff --git a/instances/devhub.near/widget/config/data.jsx b/instances/devhub.near/widget/config/data.jsx index b6abb2da2..ce40e902a 100644 --- a/instances/devhub.near/widget/config/data.jsx +++ b/instances/devhub.near/widget/config/data.jsx @@ -90,7 +90,6 @@ return { contract: "devhub.near", proposalFeedIndexerQueryName: "polyprogrammist_near_devhub_prod_v1_proposals_with_latest_snapshot", - cacheUrl: "${REPL_CACHE_URL}", indexerHasuraRole: "polyprogrammist_near", isDevhub: true, proposalFeedAnnouncement, diff --git a/instances/devhub.near/widget/devhub/components/molecule/Compose.jsx b/instances/devhub.near/widget/devhub/components/molecule/Compose.jsx index 2333a8762..0ae8c0b62 100644 --- a/instances/devhub.near/widget/devhub/components/molecule/Compose.jsx +++ b/instances/devhub.near/widget/devhub/components/molecule/Compose.jsx @@ -95,7 +95,6 @@ const Compose = ({ "${REPL_DEVHUB}/widget/devhub.components.molecule.SimpleMDE" } props={{ - instance: props.instance, data: { handler: state.handler, content: state.data }, onChange: (content) => { State.update({ data: content, handler: "update" }); @@ -164,7 +163,6 @@ const Compose = ({ "${REPL_DEVHUB}/widget/devhub.components.molecule.SimpleMDE" } props={{ - ...props, data: { handler: state.handler, content: state.data }, onChange: (content) => { State.update({ data: content, handler: "update" }); diff --git a/instances/devhub.near/widget/devhub/components/molecule/SimpleMDE.jsx b/instances/devhub.near/widget/devhub/components/molecule/SimpleMDE.jsx index 283e87206..aa4a9a04e 100644 --- a/instances/devhub.near/widget/devhub/components/molecule/SimpleMDE.jsx +++ b/instances/devhub.near/widget/devhub/components/molecule/SimpleMDE.jsx @@ -5,10 +5,6 @@ const { getLinkUsingCurrentGateway } = VM.require( "${REPL_DEVHUB}/widget/core.lib.url" ) || { getLinkUsingCurrentGateway: () => {} }; - -const instance = props.instance ?? ""; -const { cacheUrl, contract } = VM.require(`${instance}/widget/config.data`); - const data = props.data; const onChange = props.onChange ?? (() => {}); const onChangeKeyup = props.onChangeKeyup ?? (() => {}); // in case where we want immediate action @@ -34,8 +30,21 @@ const showAccountAutoComplete = props.showAutoComplete ?? false; const showProposalIdAutoComplete = props.showProposalIdAutoComplete ?? false; const autoFocus = props.autoFocus ?? false; +const queryName = "${REPL_PROPOSAL_FEED_INDEXER_QUERY_NAME}"; +const query = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${queryName}_bool_exp = {}) { +${queryName}( + offset: $offset + limit: $limit + order_by: {proposal_id: desc} + where: $where +) { + name + proposal_id +} +}`; + const proposalLink = getLinkUsingCurrentGateway( - `${contract}/widget/app?page=proposal&id=` + `${REPL_DEVHUB}/widget/app?page=proposal&id=` ); const code = ` @@ -79,11 +88,11 @@ const code = ` } .CodeMirror { - min-height:200px !important; // for autocomplete to be visible + min-height:200px !important; // for autocomplete to be visble } .CodeMirror-scroll { - min-height:200px !important; // for autocomplete to be visible + min-height:200px !important; // for autocomplete to be visble } ${embeddCSS} @@ -120,6 +129,7 @@ let isEditorInitialized = false; let followingData = {}; let profilesData = {}; let proposalLink = ''; +let query = ''; let showAccountAutoComplete = ${showAccountAutoComplete}; let showProposalIdAutoComplete = ${showProposalIdAutoComplete}; @@ -173,11 +183,12 @@ function getSuggestedAccounts(term) { return results; } -async function asyncFetch(endpoint, { method, headers }) { +async function asyncFetch(endpoint, { method, headers, body }) { try { const response = await fetch(endpoint, { method: method, headers: headers, + body: body }); if (!response.ok) { @@ -201,28 +212,46 @@ function extractNumbers(str) { return numbers; }; -function searchCacheApi(searchProposalId) { - let searchInput = encodeURI(searchProposalId); - - let searchUrl = "${cacheUrl}/proposals/search/" + searchInput; - - return asyncFetch(searchUrl, { - method: "GET", - headers: { - accept: "application/json", - }, - }).catch((error) => { - console.log("Error searching cache api", error); - }); -} - async function getSuggestedProposals(id) { let results = []; - + const variables = { + limit: 5, + offset: 0, + where: {}, + }; if (id) { - const searchResults = await searchCacheApi(id); - results = searchResults?.records || []; + const proposalId = extractNumbers(id); + if (proposalId) { + variables["where"] = { proposal_id: { _eq: id } }; + } else { + variables["where"] = { + _or: [ + { name: { _iregex: id } }, + { summary: { _iregex: id } }, + { description: { _iregex: id } }, + ], + }; + } } + await asyncFetch("https://near-queryapi.api.pagoda.co/v1/graphql", { + method: "POST", + headers: { "x-hasura-role": "${REPL_INDEXER_HASURA_ROLE}" }, + body: JSON.stringify({ + query: query, + variables: variables, + operationName: "GetLatestSnapshot", + }), + }) + .then((res) => { + const proposals = + res?.data?.[ + "${REPL_PROPOSAL_FEED_INDEXER_QUERY_NAME}" + ]; + results = proposals; + }) + .catch((error) => { + console.error(error); + }); return results; }; @@ -373,7 +402,7 @@ if (showAccountAutoComplete) { }); }); } - // show dropdown only when @ is at first place or when there is a space before @ + // show dropwdown only when @ is at first place or when there is a space before @ if (!mentionToken && (token.string === "@" && cursor.ch === 1 || token.string === "@" && cm.getTokenAt({line:cursor.line, ch: cursor.ch - 1}).string == ' ')) { mentionToken = token; mentionCursorStart = cursor; @@ -410,7 +439,6 @@ if (showProposalIdAutoComplete) { let proposalId; let referenceCursorStart; const dropdown = document.getElementById("referencedropdown"); - // Create loader element once and store it const loader = document.createElement('div'); loader.className = 'loader'; loader.textContent = 'Loading...'; @@ -432,10 +460,8 @@ if (showProposalIdAutoComplete) { const proposalIdInput = cm.getRange(referenceCursorStart, cursor); dropdown.innerHTML = ''; // Clear previous content dropdown.appendChild(loader); // Show loader - - const suggestedProposals = await getSuggestedProposals(proposalIdInput); - // Clear dropdown including loader + const suggestedProposals = await getSuggestedProposals(proposalIdInput); dropdown.innerHTML = suggestedProposals .map( (item) => @@ -470,7 +496,7 @@ if (showProposalIdAutoComplete) { } } - // show dropdown only when there is space before # or it's first char + // show dropwdown only when there is space before # or it's first char if (!proposalId && (token.string === "#" && cursor.ch === 1 || token.string === "#" && cm.getTokenAt({line:cursor.line, ch: cursor.ch - 1}).string == ' ')) { proposalId = token; referenceCursorStart = cursor; @@ -519,6 +545,9 @@ window.addEventListener("message", (event) => { if (event.data.profilesData) { profilesData = JSON.parse(event.data.profilesData); } + if (event.data.query) { + query = event.data.query; + } if (event.data.proposalLink) { proposalLink = event.data.proposalLink; } @@ -541,6 +570,7 @@ return ( content: props.data?.content ?? "", followingData, profilesData: JSON.stringify(profilesData), + query: query, handler: props.data.handler, proposalLink: proposalLink, }} diff --git a/instances/devhub.near/widget/devhub/entity/addon/blogv2/editor/form.jsx b/instances/devhub.near/widget/devhub/entity/addon/blogv2/editor/form.jsx index 3ddfe60d4..f5cbcf8d3 100644 --- a/instances/devhub.near/widget/devhub/entity/addon/blogv2/editor/form.jsx +++ b/instances/devhub.near/widget/devhub/entity/addon/blogv2/editor/form.jsx @@ -172,7 +172,6 @@ const ContentEditor = useMemo(() => { { + return asyncFetch(QUERYAPI_ENDPOINT, { + method: "POST", + headers: { "x-hasura-role": "${REPL_INDEXER_HASURA_ROLE}" }, + body: JSON.stringify({ + query: operationsDoc, + variables: variables, + operationName: operationName, + }), + }); +}; -const fetchAndSetProposalSnapshot = () => { - if (!props.proposalId) { - return; - } - asyncFetch(`${cacheUrl}/proposal/${props.proposalId}/snapshots`, { - method: "GET", - headers: { accept: "application/json" }, - }) - .then((response) => { - if (!response.ok) { - console.error(`Failed to fetch snapshots: ${response.status}`); - } - return response.body; - }) - .then((snapshots) => { - if (!Array.isArray(snapshots) || snapshots.length === 0) { - console.error("No snapshots found"); - } +const queryName = "${REPL_PROPOSAL_FEED_INDEXER_QUERY_NAME}"; +const query = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${queryName}_bool_exp = {}) { + ${queryName}( + offset: $offset + limit: $limit + order_by: {proposal_id: desc} + where: $where + ) { + block_height + } + }`; - // Get the most recent snapshot - const latestSnapshot = snapshots.reduce((latest, current) => - current.block_height > latest.block_height ? current : latest - ); - - State.update({ proposalBlockHeight: latestSnapshot.block_height }); - }) - .catch((error) => { - console.error("Failed to fetch proposal snapshot:", error); - }); +const variables = { + limit: 10, + offset, + where: { proposal_id: { _eq: proposalId } }, }; -// Fetch snapshot data on component mount -fetchAndSetProposalSnapshot(); +fetchGraphQL(query, "GetLatestSnapshot", variables).then(async (result) => { + if (result.status === 200) { + if (result.body.data) { + const data = result.body.data?.[queryName]; + State.update({ proposalBlockHeight: data[0].block_height }); + } + } +}); let acceptedTermsVersion = Near.block().header.height; diff --git a/instances/devhub.near/widget/devhub/entity/proposal/CommentsAndLogs.jsx b/instances/devhub.near/widget/devhub/entity/proposal/CommentsAndLogs.jsx index f3236305e..8da752e75 100644 --- a/instances/devhub.near/widget/devhub/entity/proposal/CommentsAndLogs.jsx +++ b/instances/devhub.near/widget/devhub/entity/proposal/CommentsAndLogs.jsx @@ -331,7 +331,7 @@ const parseProposalKeyAndValue = (key, modifiedValue, originalValue) => { accepted ); diff --git a/instances/devhub.near/widget/devhub/entity/proposal/ComposeComment.jsx b/instances/devhub.near/widget/devhub/entity/proposal/ComposeComment.jsx index fadf6240a..7e8209392 100644 --- a/instances/devhub.near/widget/devhub/entity/proposal/ComposeComment.jsx +++ b/instances/devhub.near/widget/devhub/entity/proposal/ComposeComment.jsx @@ -199,7 +199,6 @@ const Compose = useMemo(() => { { { src={ "${REPL_DEVHUB}/widget/devhub.entity.proposal.AcceptedTerms" } - props={{ ...props, proposalId: proposalId, portal: "DevHub" }} + props={{ proposalId: proposalId, portal: "DevHub" }} /> and commit to honoring it diff --git a/instances/devhub.near/widget/devhub/entity/proposal/Feed.jsx b/instances/devhub.near/widget/devhub/entity/proposal/Feed.jsx index a609d8983..a10e52241 100644 --- a/instances/devhub.near/widget/devhub/entity/proposal/Feed.jsx +++ b/instances/devhub.near/widget/devhub/entity/proposal/Feed.jsx @@ -10,7 +10,7 @@ const { proposalFeedAnnouncement, availableCategoryOptions, proposalFeedIndexerQueryName, - cacheUrl, + indexerHasuraRole, isDevhub, isInfra, isEvents, @@ -193,7 +193,8 @@ const FeedItem = ({ proposal, index }) => {
@@ -268,18 +269,14 @@ const getProposal = (proposal_id) => { }); }; -const getRfp = (rfp_id) => { - return Near.asyncView(contract, "get_rfp", { - rfp_id, - }); -}; - const FeedPage = () => { + const QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql`; + State.init({ data: [], author: "", stage: "", - sort: "id_desc", + sort: "", category: "", input: "", loading: false, @@ -289,76 +286,120 @@ const FeedPage = () => { currentlyDisplaying: 0, }); - const makeMoreItems = () => { - State.update({ makeMoreLoader: true }); - fetchProposals(state.data.length); - }; - - function searchCacheApi() { - let searchTerm = state.input; - let searchInput = encodeURI(searchTerm); - let searchUrl = `${cacheUrl}/proposals/search/${searchInput}`; - - return asyncFetch(searchUrl, { - method: "GET", - headers: { - accept: "application/json", - }, - }).catch((error) => { - console.log("Error searching cache api", error); + const query = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${proposalFeedIndexerQueryName}_bool_exp = {}) { + ${proposalFeedIndexerQueryName}( + offset: $offset + limit: $limit + order_by: {proposal_id: desc} + where: $where + ) { + author_id + block_height + name + category + summary + editor_id + proposal_id + ts + timeline + views + labels + linked_rfp + } + ${proposalFeedIndexerQueryName}_aggregate( + order_by: {proposal_id: desc} + where: $where + ) { + aggregate { + count + } + } + }`; + + const rfpQuery = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${rfpFeedIndexerQueryName}_bool_exp = {}) { + ${rfpFeedIndexerQueryName}( + offset: $offset + limit: $limit + order_by: {rfp_id: desc} + where: $where + ) { + name + rfp_id + } + }`; + + function fetchGraphQL(operationsDoc, operationName, variables) { + return asyncFetch(QUERYAPI_ENDPOINT, { + method: "POST", + headers: { "x-hasura-role": indexerHasuraRole }, + body: JSON.stringify({ + query: operationsDoc, + variables: variables, + operationName: operationName, + }), }); } - function searchProposals() { - if (state.loading) return; - State.update({ loading: true }); + function separateNumberAndText(str) { + const numberRegex = /\d+/; - searchCacheApi().then((result) => { - let body = result.body; - - const promises = body.records.map((proposal) => { - if (isNumber(proposal.linked_rfp)) { - getRfp(proposal.linked_rfp).then((rfp) => { - return { ...proposal, rfpData: rfp }; - }); - } else { - return Promise.resolve(proposal); - } - }); - Promise.all(promises).then((proposalsWithRfpData) => { - State.update({ aggregatedCount: body.total_records }); - fetchBlockHeights(proposalsWithRfpData, 0); - }); - }); + if (numberRegex.test(str)) { + const number = str.match(numberRegex)[0]; + const text = str.replace(numberRegex, "").trim(); + return { number: parseInt(number), text }; + } else { + return { number: null, text: str.trim() }; + } } - function fetchCacheApi(variables) { - let fetchUrl = `${cacheUrl}/proposals?order=${variables.order}&limit=${variables.limit}&offset=${variables.offset}`; - - if (variables.author_id) { - fetchUrl += `&filters.author_id=${variables.author_id}`; - } - if (variables.stage) { - fetchUrl += `&filters.stage=${variables.stage}`; + const buildWhereClause = () => { + let where = {}; + if (state.author) { + where = { author_id: { _eq: state.author }, ...where }; } - if (variables.category) { + + if (state.category) { if (isInfra || isEvents) { - fetchUrl += `&filters.labels=${variables.category}`; + where = { labels: { _contains: state.category }, ...where }; } else { - fetchUrl += `&filters.category=${variables.category}`; + where = { category: { _eq: state.category }, ...where }; } } - return asyncFetch(fetchUrl, { - method: "GET", - headers: { - accept: "application/json", - }, - }).catch((error) => { - console.log("Error fetching cache api", error); - }); - } - function fetchProposals(offset) { + if (state.stage) { + // timeline is stored as jsonb + where = { + timeline: { _cast: { String: { _regex: `${state.stage}` } } }, + ...where, + }; + } + if (state.input) { + const { number, text } = separateNumberAndText(state.input); + if (number) { + where = { proposal_id: { _eq: number }, ...where }; + } + + if (text) { + where = { + _or: [ + { name: { _iregex: `${text}` } }, + { summary: { _iregex: `${text}` } }, + { description: { _iregex: `${text}` } }, + ], + ...where, + }; + } + } + + return where; + }; + + const makeMoreItems = () => { + State.update({ makeMoreLoader: true }); + fetchProposals(state.data.length); + }; + + const fetchProposals = (offset) => { if (!offset) { offset = 0; } @@ -366,30 +407,36 @@ const FeedPage = () => { State.update({ loading: true }); const FETCH_LIMIT = 10; const variables = { - order: state.sort, limit: FETCH_LIMIT, offset, - category: state.category ? encodeURIComponent(state.category) : "", - author_id: state.author ? encodeURIComponent(state.author) : "", - stage: state.stage ? encodeURIComponent(state.stage) : "", + where: buildWhereClause(), }; - fetchCacheApi(variables).then((result) => { - const body = result.body; - const promises = body.records.map((proposal) => { - if (isNumber(proposal.linked_rfp)) { - getRfp(proposal.linked_rfp).then((rfp) => { - return { ...proposal, rfpData: rfp }; + fetchGraphQL(query, "GetLatestSnapshot", variables).then(async (result) => { + if (result.status === 200) { + if (result.body.data) { + const data = result.body.data[proposalFeedIndexerQueryName]; + const totalResult = + result.body.data[`${proposalFeedIndexerQueryName}_aggregate`]; + const promises = data.map((item) => { + if (isNumber(item.linked_rfp)) { + return fetchGraphQL(rfpQuery, "GetLatestSnapshot", { + where: { rfp_id: { _eq: item.linked_rfp } }, + }).then((result) => { + const rfpData = result.body.data?.[rfpFeedIndexerQueryName]; + return { ...item, rfpData: rfpData[0] }; + }); + } else { + return Promise.resolve(item); + } + }); + Promise.all(promises).then((res) => { + State.update({ aggregatedCount: totalResult.aggregate.count }); + fetchBlockHeights(res, offset); }); - } else { - return Promise.resolve(proposal); } - }); - Promise.all(promises).then((proposalsWithRfpData) => { - State.update({ aggregatedCount: body.total_records }); - fetchBlockHeights(proposalsWithRfpData, offset); - }); + } }); - } + }; useEffect(() => { State.update({ searchLoader: true }); @@ -401,45 +448,48 @@ const FeedPage = () => { ...new Set([...newItems, ...state.data].map((i) => JSON.stringify(i))), ].map((i) => JSON.parse(i)); // Sorting in the front end - if (state.sort === "id_desc" || state.sort === "") { + if (state.sort === "proposal_id" || state.sort === "") { items.sort((a, b) => b.proposal_id - a.proposal_id); + } else if (state.sort === "views") { + items.sort((a, b) => b.views - a.views); } return items; }; const fetchBlockHeights = (data, offset) => { - data = data.map((item, index) => ({ - ...item, - timeline: JSON.parse(item.timeline), - })); - if (offset) { - let newData = mergeItems(data); - State.update({ - data: newData, - currentlyDisplaying: newData.length, - loading: false, - searchLoader: false, - makeMoreLoader: false, - }); - } else { - State.update({ - data, - currentlyDisplaying: data.length, - loading: false, - searchLoader: false, - makeMoreLoader: false, - }); - } + let promises = data.map((item) => getProposal(item.proposal_id)); + Promise.all(promises).then((blockHeights) => { + data = data.map((item, index) => ({ + ...item, + timeline: JSON.parse(item.timeline), + social_db_post_block_height: + blockHeights[index].social_db_post_block_height, + })); + if (offset) { + let newData = mergeItems(data); + State.update({ + data: newData, + currentlyDisplaying: newData.length, + loading: false, + searchLoader: false, + makeMoreLoader: false, + }); + } else { + State.update({ + data, + currentlyDisplaying: data.length, + loading: false, + searchLoader: false, + makeMoreLoader: false, + }); + } + }); }; useEffect(() => { const handler = setTimeout(() => { - if (state.input) { - searchProposals(); - } else { - fetchProposals(); - } + fetchProposals(); }, 1000); return () => { diff --git a/instances/devhub.near/widget/devhub/entity/proposal/LinkedProposalsDropdown.jsx b/instances/devhub.near/widget/devhub/entity/proposal/LinkedProposalsDropdown.jsx index 995a38a0a..53d6eba9e 100644 --- a/instances/devhub.near/widget/devhub/entity/proposal/LinkedProposalsDropdown.jsx +++ b/instances/devhub.near/widget/devhub/entity/proposal/LinkedProposalsDropdown.jsx @@ -1,15 +1,24 @@ const { href } = VM.require("${REPL_DEVHUB}/widget/core.lib.url"); href || (href = () => {}); -const instance = props.instance ?? ""; - -const { cacheUrl, contract } = VM.require(`${instance}/widget/config.data`); - const linkedProposals = props.linkedProposals; const onChange = props.onChange; const [selectedProposals, setSelectedProposals] = useState(linkedProposals); const [proposalsOptions, setProposalsOptions] = useState([]); -const [textAfterHash, setTextAfterHash] = useState(""); +const [searchProposalId, setSearchProposalId] = useState(""); +const QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql`; +const queryName = "${REPL_PROPOSAL_FEED_INDEXER_QUERY_NAME}"; +const query = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${queryName}_bool_exp = {}) { +${queryName}( + offset: $offset + limit: $limit + order_by: {proposal_id: desc} + where: $where +) { + name + proposal_id +} +}`; useEffect(() => { if (JSON.stringify(linkedProposals) !== JSON.stringify(selectedProposals)) { @@ -23,43 +32,73 @@ useEffect(() => { } }, [selectedProposals]); -function searchCacheApi(input) { - let searchInput = encodeURI(input); - let searchUrl = `${cacheUrl}/proposals/search/${searchInput}`; +function separateNumberAndText(str) { + const numberRegex = /\d+/; - return asyncFetch(searchUrl, { - method: "GET", - headers: { - accept: "application/json", - }, - }).catch((error) => { - console.log("Error searching cache api", error); - }); + if (numberRegex.test(str)) { + const number = str.match(numberRegex)[0]; + const text = str.replace(numberRegex, "").trim(); + return { number: parseInt(number), text }; + } else { + return { number: null, text: str.trim() }; + } } -function searchProposals(input) { - if (state.loading) return; - State.update({ loading: true }); +const buildWhereClause = () => { + let where = {}; + const { number, text } = separateNumberAndText(searchProposalId); - searchCacheApi(input).then((result) => { - let proposalsData = result.body.records; + if (number) { + where = { proposal_id: { _eq: number }, ...where }; + } - const data = []; - for (const prop of proposalsData) { - data.push({ - label: "# " + prop.proposal_id + " : " + prop.name, - value: prop.proposal_id, - }); - } - setProposalsOptions(data); + if (text) { + where = { name: { _ilike: `%${text}%` }, ...where }; + } + + return where; +}; + +function fetchGraphQL(operationsDoc, operationName, variables) { + return asyncFetch(QUERYAPI_ENDPOINT, { + method: "POST", + headers: { "x-hasura-role": "${REPL_INDEXER_HASURA_ROLE}" }, + body: JSON.stringify({ + query: operationsDoc, + variables: variables, + operationName: operationName, + }), }); } +const fetchProposals = () => { + const FETCH_LIMIT = 30; + const variables = { + limit: FETCH_LIMIT, + offset: 0, + where: buildWhereClause(), + }; + fetchGraphQL(query, "GetLatestSnapshot", variables).then(async (result) => { + if (result.status === 200) { + if (result.body.data) { + const proposalsData = result.body.data[queryName]; + + const data = []; + for (const prop of proposalsData) { + data.push({ + label: "# " + prop.proposal_id + " : " + prop.name, + value: prop.proposal_id, + }); + } + setProposalsOptions(data); + } + } + }); +}; + useEffect(() => { - if (textAfterHash.trim()) { - searchProposals(textAfterHash); - } -}, [textAfterHash]); + fetchProposals(); +}, [searchProposalId]); return ( <> @@ -69,7 +108,7 @@ return ( - +
); @@ -98,7 +137,7 @@ return ( { if (!selectedProposals.some((item) => item.value === v.value)) { setSelectedProposals([...selectedProposals, v]); @@ -110,7 +149,7 @@ return ( defaultLabel: "Search proposals", searchByValue: true, onSearch: (value) => { - setTextAfterHash(value); + setSearchProposalId(value); }, }} /> diff --git a/instances/devhub.near/widget/devhub/entity/proposal/Proposal.jsx b/instances/devhub.near/widget/devhub/entity/proposal/Proposal.jsx index 8c92be1b0..8c2182381 100644 --- a/instances/devhub.near/widget/devhub/entity/proposal/Proposal.jsx +++ b/instances/devhub.near/widget/devhub/entity/proposal/Proposal.jsx @@ -939,7 +939,6 @@ return ( } props={{ ...props, - instance: props.instance, item: item, notifyAccountId: authorId, id: proposal.id, diff --git a/instances/devhub.near/widget/devhub/feature/proposal-search/by-sort.jsx b/instances/devhub.near/widget/devhub/feature/proposal-search/by-sort.jsx index 192361033..7fa65fca0 100644 --- a/instances/devhub.near/widget/devhub/feature/proposal-search/by-sort.jsx +++ b/instances/devhub.near/widget/devhub/feature/proposal-search/by-sort.jsx @@ -1,9 +1,11 @@ const options = [ { label: "All", value: "" }, - { label: "Most recent", value: "id_desc" }, // proposal_id desc - { label: "Oldest", value: "id_asc" }, // proposal_id desc - { label: "Recently updated", value: "ts_desc" }, // timestamp desc - { label: "Least recently updated", value: "ts_asc" }, // timestamp asc + { label: "Most recent", value: "proposal_id" }, // proposal_id desc + { label: "Most viewed", value: "views" }, // views desc + // TODO add track_comments function to devhub to track it in the indexer + // { label: "Most commented", value: "" }, // comments desc + // { label: "Unanswered", value: "" }, // where comments = 0 + // where comments = 0 ]; const setSelected = props.onStateChange ?? (() => {}); diff --git a/instances/events-committee.near/aliases.mainnet.json b/instances/events-committee.near/aliases.mainnet.json index 39dc1ec40..efd479a11 100644 --- a/instances/events-committee.near/aliases.mainnet.json +++ b/instances/events-committee.near/aliases.mainnet.json @@ -11,6 +11,5 @@ "REPL_SOCIAL_CONTRACT": "social.near", "REPL_RPC_URL": "https://rpc.mainnet.near.org", "REPL_PROPOSAL_FEED_INDEXER_QUERY_NAME": "thomasguntenaar_near_event_committee_prod_v1_proposals_with_latest_snapshot", - "REPL_INDEXER_HASURA_ROLE": "thomasguntenaar_near", - "REPL_CACHE_URL": "https://events-cache-api-rs.fly.dev" + "REPL_INDEXER_HASURA_ROLE": "thomasguntenaar_near" } diff --git a/instances/events-committee.near/widget/app.jsx b/instances/events-committee.near/widget/app.jsx index 63d2bcdb6..e8969533e 100644 --- a/instances/events-committee.near/widget/app.jsx +++ b/instances/events-committee.near/widget/app.jsx @@ -34,7 +34,7 @@ function Page() { return ( ); } @@ -42,7 +42,7 @@ function Page() { return ( ); } @@ -50,7 +50,7 @@ function Page() { return ( ); } diff --git a/instances/events-committee.near/widget/config/data.jsx b/instances/events-committee.near/widget/config/data.jsx index f9445cfb1..b16e70e46 100644 --- a/instances/events-committee.near/widget/config/data.jsx +++ b/instances/events-committee.near/widget/config/data.jsx @@ -46,7 +46,6 @@ return { proposalFeedIndexerQueryName: "thomasguntenaar_near_event_committee_prod_v1_proposals_with_latest_snapshot", indexerHasuraRole: "thomasguntenaar_near", - cacheUrl: "${REPL_CACHE_URL}", isEvents: true, proposalFeedAnnouncement, availableCategoryOptions: categoryOptions, diff --git a/instances/events-committee.near/widget/devhub/components/molecule/Compose.jsx b/instances/events-committee.near/widget/devhub/components/molecule/Compose.jsx index 6b4621125..637f4c8dc 100644 --- a/instances/events-committee.near/widget/devhub/components/molecule/Compose.jsx +++ b/instances/events-committee.near/widget/devhub/components/molecule/Compose.jsx @@ -85,9 +85,8 @@ const Compose = ({ {state.selectedTab === "editor" ? ( <> { State.update({ data: content, handler: "update" }); diff --git a/instances/events-committee.near/widget/devhub/components/molecule/SimpleMDE.jsx b/instances/events-committee.near/widget/devhub/components/molecule/SimpleMDE.jsx new file mode 100644 index 000000000..d5de9fecd --- /dev/null +++ b/instances/events-committee.near/widget/devhub/components/molecule/SimpleMDE.jsx @@ -0,0 +1,598 @@ +/** + * iframe embedding a SimpleMDE component + * https://github.com/sparksuite/simplemde-markdown-editor + */ +const { getLinkUsingCurrentGateway } = VM.require( + "${REPL_DEVHUB}/widget/core.lib.url" +) || { getLinkUsingCurrentGateway: () => {} }; +const data = props.data; +const onChange = props.onChange ?? (() => {}); +const onChangeKeyup = props.onChangeKeyup ?? (() => {}); // in case where we want immediate action +const height = props.height ?? "390"; +const className = props.className ?? "w-100"; +const embeddCSS = props.embeddCSS; +const sortedRelevantUsers = props.sortedRelevantUsers || []; + +State.init({ + iframeHeight: height, + message: props.data, +}); + +const profilesData = Social.get("*/profile/name", "final") ?? {}; +const followingData = + Social.get(`${context.accountId}/graph/follow/**`, "final") ?? {}; + +// SIMPLEMDE CONFIG // +const fontFamily = props.fontFamily ?? "sans-serif"; +const alignToolItems = props.alignToolItems ?? "right"; +const placeholder = props.placeholder ?? ""; +const showAccountAutoComplete = props.showAutoComplete ?? false; +const showProposalIdAutoComplete = props.showProposalIdAutoComplete ?? false; +const autoFocus = props.autoFocus ?? false; + +const queryName = "${REPL_PROPOSAL_FEED_INDEXER_QUERY_NAME}"; +const query = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${queryName}_bool_exp = {}) { +${queryName}( + offset: $offset + limit: $limit + order_by: {proposal_id: desc} + where: $where +) { + name + proposal_id +} +}`; + +const proposalLink = getLinkUsingCurrentGateway( + `${REPL_EVENTS}/widget/app?page=proposal&id=` +); + +const code = ` + + + + + + + + + + + + + + + + + + + + + + + +`; + +return ( +