diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c9b23144..1c675c4a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,35 +1,17 @@ name: Deploy Components to Mainnet + on: push: branches: [main] -jobs: - deploy-widgets: - runs-on: ubuntu-latest - name: Deploy widgets to social.near (mainnet) - env: - BOS_DEPLOY_ACCOUNT_ID: ${{ vars.BOS_DEPLOY_ACCOUNT_ID }} - BOS_SIGNER_ACCOUNT_ID: ${{ vars.BOS_SIGNER_ACCOUNT_ID }} - BOS_SIGNER_PUBLIC_KEY: ${{ vars.BOS_SIGNER_PUBLIC_KEY }} - BOS_SIGNER_PRIVATE_KEY: ${{ secrets.BOS_SIGNER_PRIVATE_KEY }} - NETWORK_ID: mainnet - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - name: Install near-social CLI - run: | - curl --proto '=https' --tlsv1.2 -LsSf https://github.com/FroVolod/bos-cli-rs/releases/download/v0.3.1/bos-cli-v0.3.1-installer.sh | sh - - - name: Install bos-workspace from dev branch - run: | - npm install git+https://github.com/nearbuilders/bos-workspace.git#main - - - name: Build the workspaces - run: | - npm run bosworkspace build - - - name: Deploy widgets - working-directory: ./build/homepage - run: | - bos components deploy "$BOS_DEPLOY_ACCOUNT_ID" sign-as "$BOS_SIGNER_ACCOUNT_ID" network-config mainnet sign-with-plaintext-private-key --signer-public-key "$BOS_SIGNER_PUBLIC_KEY" --signer-private-key "$BOS_SIGNER_PRIVATE_KEY" send +jobs: + deploy-mainnet: + uses: nearbuilders/bos-workspace/.github/workflows/deploy.yml@main + with: + deploy-env: "mainnet" + app-name: "builddao" + deploy-account-address: ${{ vars.BOS_SIGNER_ACCOUNT_ID }} + signer-account-address: ${{ vars.BOS_SIGNER_ACCOUNT_ID }} + signer-public-key: ${{ vars.BOS_SIGNER_PUBLIC_KEY }} + secrets: + SIGNER_PRIVATE_KEY: ${{ secrets.BOS_SIGNER_PRIVATE_KEY }} \ No newline at end of file diff --git a/.github/workflows/testnet.yml b/.github/workflows/testnet.yml index e39eacc5..2b2530fd 100644 --- a/.github/workflows/testnet.yml +++ b/.github/workflows/testnet.yml @@ -1,35 +1,17 @@ name: Deploy Components to Testnet + on: push: - branches: [feature/testnet-deploy] -jobs: - deploy-widgets: - runs-on: ubuntu-latest - name: Deploy widgets to social.near (testnet) - env: - BOS_DEPLOY_ACCOUNT_ID: ${{ vars.TESTNET_DEPLOY_ACCOUNT_ID }} - BOS_SIGNER_ACCOUNT_ID: ${{ vars.TESTNET_SIGNER_ACCOUNT_ID }} - BOS_SIGNER_PUBLIC_KEY: ${{ vars.TESTNET_SIGNER_PUBLIC_KEY }} - BOS_SIGNER_PRIVATE_KEY: ${{ secrets.TESTNET_SIGNER_PRIVATE_KEY }} - NETWORK_ID: testnet - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Install near-social CLI - run: | - curl --proto '=https' --tlsv1.2 -LsSf https://github.com/FroVolod/bos-cli-rs/releases/download/v0.3.1/bos-cli-v0.3.1-installer.sh | sh + branches: [develop] - - name: Install bos-workspace from dev branch - run: | - npm install https://github.com/NEARBuilders/bos-workspace.git#dev - - - name: Build the workspaces - run: | - npm run bosworkspace build - - - name: Deploy widgets - working-directory: ./build/canvas - run: | - bos components deploy "$BOS_DEPLOY_ACCOUNT_ID" sign-as "$BOS_SIGNER_ACCOUNT_ID" network-config testnet sign-with-plaintext-private-key --signer-public-key "$BOS_SIGNER_PUBLIC_KEY" --signer-private-key "$BOS_SIGNER_PRIVATE_KEY" send +jobs: + deploy-testnet: + uses: nearbuilders/bos-workspace/.github/workflows/deploy.yml@main + with: + deploy-env: "testnet" + app-name: "builddao" + deploy-account-address: ${{ vars.BOS_TESTNET_SIGNER_ACCOUNT_ID }} + signer-account-address: ${{ vars.BOS_TESTNET_SIGNER_ACCOUNT_ID }} + signer-public-key: ${{ vars.BOS_TESTNET_SIGNER_PUBLIC_KEY }} + secrets: + SIGNER_PRIVATE_KEY: ${{ secrets.BOS_TESTNET_SIGNER_PRIVATE_KEY }} \ No newline at end of file diff --git a/apps/homepage/bos.config.json b/apps/builddao/bos.config.json similarity index 100% rename from apps/homepage/bos.config.json rename to apps/builddao/bos.config.json diff --git a/apps/builddao/widget/Compose.jsx b/apps/builddao/widget/Compose.jsx new file mode 100644 index 00000000..26ca8d6f --- /dev/null +++ b/apps/builddao/widget/Compose.jsx @@ -0,0 +1,306 @@ +const [view, setView] = useState("editor"); +const [postContent, setPostContent] = useState(props.template); +const [hideAdvanced, setHideAdvanced] = useState(true); +const [labels, setLabels] = useState([]); + +function generateUID() { + const maxHex = 0xffffffff; + const randomNumber = Math.floor(Math.random() * maxHex); + return randomNumber.toString(16).padStart(8, "0"); +} + +function tagsFromLabels(labels) { + return labels.reduce( + (newLabels, label) => ({ + ...newLabels, + [label]: "", + }), + {} + ); +} + +const postToCustomFeed = ({ feed, text, labels }) => { + const postId = generateUID(); + if (!labels) labels = []; + return Social.set( + { + update: { + [postId]: { + "": JSON.stringify({ + type: "md", + text, + labels, + }), + metadata: { + type: feed.name, + tags: tagsFromLabels(labels), + }, + }, + }, + post: { + main: JSON.stringify({ + type: "md", + text: `[EMBED](${context.accountId}/${feed.name}/${postId})`, + }), + }, + index: { + post: JSON.stringify({ key: "main", value: { type: "md" } }), + every: JSON.stringify({ key: feed.name, value: { type: "md" } }), + }, + }, + { + force: true, + onCommit: () => { + console.log(`Commited ${feed}: #${postId}`); + }, + onCancel: () => { + console.log(`Cancelled ${feed}: #${postId}`); + }, + } + ); +}; + +const PostCreator = styled.div` + display: flex; + flex-direction: column; + gap: 1.5rem; + + padding: 1rem; + background: #23242b; + border-radius: 12px; + + margin-bottom: 1rem; +`; + +const MarkdownEditor = ` + * { + border: none !important; + } + + .rc-md-editor { + background: #4f5055; + border-top: 1px solid #4f5055 !important; + } + + .editor-container { + background: #4f5055; + } + + .drop-wrap { + top: -110px !important; + border-radius: 0.5rem !important; + } + + .header-list { + display: flex; + align-items: center; + } + + textarea { + background: #23242b !important; + color: #fff !important; + + font-family: sans-serif !important; + font-size: 1rem; + + border: 1px solid #4f5055 !important; + border-top: 0 !important; + } + + .rc-md-navigation { + background: #23242b !important; + border: 1px solid #4f5055 !important; + border-top: 0 !important; + border-bottom: 0 !important; + + i { + color: #cdd0d5; + } + } +`; + +const Button = styled.button` + all: unset; + display: flex; + padding: 10px 20px; + justify-content: center; + align-items: center; + gap: 4px; + + border-radius: 8px; + background: var(--Yellow, #ffaf51); + + color: var(--black-100, #000); + + /* Other/Button_text */ + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: normal; +`; + +const SecondaryButton = styled.button` + all: unset; + display: flex; + padding: 10px 20px; + justify-content: center; + align-items: center; + gap: 4px; + + border-radius: 8px; + border: 1px solid var(--white-100, #fff); + background: transparent; + color: var(--white-100, #fff); + + /* Other/Button_text */ + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: normal; + + transition: all 300ms; + + &:hover { + background: #fff; + color: #000; + } +`; + +const MarkdownPreview = styled.div` + * { + color: #b6b6b8 !important; + } +`; + +const LabelSelect = styled.div` + label { + color: #fff; + } + + .rbt-input-multi { + background: #23242b !important; + color: #fff !important; + } + + .rbt-token { + background: #202020 !important; + color: #fff !important; + } + + .rbt-menu { + background: #23242b !important; + color: #fff !important; + + .dropdown-item { + color: #fff !important; + transition: all 300ms; + + &:hover { + background: #202020; + } + } + } +`; + +return ( + +
+ +
+

{name}

+

@{context.accountId}

+
+
+ +
+ {view === "editor" ? ( + + ) : ( + + + + )} +
+ + {view === "editor" && ( +
+
setHideAdvanced(!hideAdvanced)} + style={{ cursor: "pointer" }} + > +
Advanced
+ +
+ + + + +
+ )} + +
+ setView(view === "editor" ? "preview" : "editor")} + > + {view === "editor" ? ( + <> + Preview + + ) : ( + <> + Edit + + )} + + +
+
+); diff --git a/apps/builddao/widget/Feed.jsx b/apps/builddao/widget/Feed.jsx new file mode 100644 index 00000000..fe067ae4 --- /dev/null +++ b/apps/builddao/widget/Feed.jsx @@ -0,0 +1,116 @@ +const { Feed } = VM.require("devs.near/widget/Module.Feed"); + +Feed = Feed || (() => <>); // make sure you have this or else it can break + +const Container = styled.div` + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 1rem; + + @media screen and (max-width: 768px) { + display: flex; + flex-direction: column; + } +`; + +const Aside = styled.div` + grid-column: span 1 / span 1; +`; + +const MainContent = styled.div` + grid-column: span 4 / span 4; +`; + +const [currentFeed, setCurrentFeed] = useState("updates"); +const [template, setTemplate] = useState("What did you have in mind?"); + +const CustomFeed = ({ name }) => { + name = !!name ? name : "update"; + return ( + ( + } + src="/*__@appAccount__*//widget/components.post.post" + props={{ + accountId: p.accountId, + blockHeight: p.blockHeight, + noBorder: true, + }} + /> + )} + /> + ); +}; + +function formatDate(date) { + const options = { year: "numeric", month: "short", day: "numeric" }; + return date.toLocaleDateString("en-US", options); +} + +const feedsDict = { + updates: { + key: "updates", + name: "update", + template: `🔔 DAILY UPDATE: ${formatDate(new Date())} + +📆 YESTERDAY: +- yesterday I did this (hyperlink proof) +- I also did this + +💻 WHAT I AM DOING TODAY: +- task 1 + +🛑 BLOCKERS: +- @anyone that is causing a blocker or outline any blockers in general`, + }, +}; + +const feeds = Object.keys(feedsDict); + +const feed = () => { + if (!!feedsDict[currentFeed]) { + return CustomFeed(feedsDict[currentFeed]); + } +}; + +return ( + + + + {context.accountId && ( + + )} + {feed()} + + +); diff --git a/apps/homepage/widget/JoinButton.jsx b/apps/builddao/widget/JoinButton.jsx similarity index 100% rename from apps/homepage/widget/JoinButton.jsx rename to apps/builddao/widget/JoinButton.jsx diff --git a/apps/homepage/widget/MetadataEditor.jsx b/apps/builddao/widget/MetadataEditor.jsx similarity index 100% rename from apps/homepage/widget/MetadataEditor.jsx rename to apps/builddao/widget/MetadataEditor.jsx diff --git a/apps/homepage/widget/WidgetMetadata.jsx b/apps/builddao/widget/WidgetMetadata.jsx similarity index 100% rename from apps/homepage/widget/WidgetMetadata.jsx rename to apps/builddao/widget/WidgetMetadata.jsx diff --git a/apps/homepage/widget/WidgetMetadataEditor.jsx b/apps/builddao/widget/WidgetMetadataEditor.jsx similarity index 100% rename from apps/homepage/widget/WidgetMetadataEditor.jsx rename to apps/builddao/widget/WidgetMetadataEditor.jsx diff --git a/apps/builddao/widget/aside.jsx b/apps/builddao/widget/aside.jsx new file mode 100644 index 00000000..d8450789 --- /dev/null +++ b/apps/builddao/widget/aside.jsx @@ -0,0 +1,132 @@ +const UpdateIcon = ( + + + +); + +const BugIcon = ( + + + +); + +const ResourceIcon = ( + + + +); + +const Container = styled.div` + border-radius: 16px; + border: 1px solid var(--Stroke-color, rgba(255, 255, 255, 0.2)); + background: var(--bg-1, #0b0c14); + width: 100%; + height: 100%; + + display: flex; + padding: 24px 12px; + flex-direction: column; + align-items: flex-start; + gap: 16px; + + @media screen and (max-width: 768px) { + border: 0px; + flex-direction: row; + overflow-x: auto; + } +`; + +const TabButton = styled.button` + all: unset; + display: flex; + padding: 8px 12px; + align-items: center; + gap: 10px; + align-self: stretch; + + @media screen and (max-width: 768px) { + align-self: auto; + } + + border-radius: 8px; + border: 1px solid var(--Stroke-color, rgba(255, 255, 255, 0.2)); + + color: var(--white-50, rgba(255, 255, 255, 0.7)); + + /* Body/14px */ + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 170%; /* 23.8px */ + + transition: all 300ms; + + &:hover, + &:active, + &.active { + border-radius: 8px; + background: var(--Yellow, #ffaf51) !important; + + color: var(--black-100, #000) !important; + cursor: pointer; + + /* Body/14px */ + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 170%; /* 23.8px */ + + svg { + filter: invert(1); + } + } +`; + +function capitalize (s) { + return s + .split(' ') + .map(word => `${word.slice(0,1).toUpperCase()}${word.slice(1)}`) + .reduce((phrase, word) => `${phrase} ${word}`) +} + +return ( + + { props.feeds.map((name) => ( + props.setCurrentFeed(name)} + > + {UpdateIcon} {capitalize(name)} + + ))} + +); diff --git a/apps/homepage/widget/components/button/join-now.jsx b/apps/builddao/widget/components/button/join-now.jsx similarity index 100% rename from apps/homepage/widget/components/button/join-now.jsx rename to apps/builddao/widget/components/button/join-now.jsx diff --git a/apps/builddao/widget/components/post/post-header.jsx b/apps/builddao/widget/components/post/post-header.jsx new file mode 100644 index 00000000..d08ddf0b --- /dev/null +++ b/apps/builddao/widget/components/post/post-header.jsx @@ -0,0 +1,169 @@ +const accountId = props.accountId; +const blockHeight = props.blockHeight; +const pinned = !!props.pinned; +const hideMenu = !!props.hideMenu; +const name = Social.get(`${accountId}/profile/name`); + +const postType = props.postType ?? "post"; +const link = props.link; +const isPremium = !!props.isPremium; + +const Overlay = (props) => ( + + + +); + +const DotsSvg = ( + + + +); + +const Button = styled.div` + line-height: 20px; + min-height: 20px; + display: inline-flex; + align-items: center; + justify-content: left; + background: inherit; + color: #6c757d; + font-size: 16px; + .icon { + position: relative; + &:before { + margin: -8px; + content: ""; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + border-radius: 50%; + } + } + + &:not([disabled]) { + cursor: pointer; + } + + &:not([disabled]):hover { + opacity: 1 !important; + color: DeepSkyBlue; + + .icon:before { + background: rgba(0, 191, 255, 0.1); + } + } +`; + +return ( +
+
+
+
+ +
+
+
+ {name && ( + +
{name}
+
+ )} +
+ +
+
+
+
+ +
@{accountId}
+
+
+ {!pinned && ( +
+ {blockHeight === "now" ? ( + "now" + ) : ( + + + + )} +
+ )} +
+
+
+
+ {pinned && ( + + + + )} + {!pinned && !hideMenu && blockHeight !== "now" && ( + + + + + )} +
+); diff --git a/apps/builddao/widget/components/post/post.jsx b/apps/builddao/widget/components/post/post.jsx new file mode 100644 index 00000000..ac9ae475 --- /dev/null +++ b/apps/builddao/widget/components/post/post.jsx @@ -0,0 +1,371 @@ +const accountId = props.accountId; +if (!accountId) { + return "No accountId"; +} +const blockHeight = + props.blockHeight === "now" ? "now" : parseInt(props.blockHeight); +const pinned = !!props.pinned; +const hideMenu = !!props.hideMenu; +const hideButtons = !!props.hideButtons; +const content = + props.content ?? + JSON.parse(Social.get(`${accountId}/post/main`, blockHeight) ?? "null"); +const subscribe = !!props.subscribe; +const raw = !!props.raw; +const groupId = props.groupId ?? content.groupId; +const indexKey = props.indexKey; +const permissions = props.permissions; +const fullPostLink = props.fullPostLink; + +const notifyAccountId = accountId; +const item = { + type: "social", + path: `${accountId}/post/main`, + blockHeight, +}; + +const link = + props.link ?? + props.fullPostLink ?? + `/mob.near/widget/MainPage.N.Post.Page?accountId=${accountId}&blockHeight=${blockHeight}`; + +const StyledPost = styled.div` + margin-bottom: 1rem; + .post { + border-radius: 16px; + border: 1px solid var(--Stroke-color, rgba(255, 255, 255, 0.2)); + color: #b6b6b8; + padding: 24px !important; + background-color: #0b0c14; + transition: all 300ms; + + &:hover { + background-color: #171929 !important; + .expand-post { + background-image: linear-gradient( + to bottom, + rgb(23, 25, 41, 0), + rgb(23, 25, 41, 1) 25% + ) !important; + } + } + + .post-header { + span, + .text-muted { + color: #fff !important; + } + } + + .buttons { + border-top: 1px solid #3c3d43; + padding: 0.5rem; + } + + .expand-post { + background-image: linear-gradient( + to bottom, + rgb(11, 12, 20, 0), + rgb(11, 12, 20, 1) 25% + ) !important; + } + } + + .dropdown-menu { + background-color: #0b0c14 !important; + color: #fff !important; + + li.dropdown-item { + color: #fff !important; + &:hover { + a { + color: #0b0c14 !important; + } + } + } + + .link-dark, + .dropdown-item { + color: #fff !important; + + &:hover { + color: #0b0c14 !important; + + span { + color: #0b0c14 !important; + } + } + } + } + + textarea { + color: #b6b6b8 !important; + } +`; + +const Wrapper = styled.div` + margin: 0 -12px; + line-height: normal; + + .post { + position: relative; + padding: 12px; + padding-bottom: 4px; + display: flex; + h1, + h2, + h3, + h4, + h5, + h6 { + font-size: 16px !important; + } + @media (max-width: 767px) { + font-size: 15px !important; + h1, + h2, + h3, + h4, + h5, + h6 { + font-size: 15px !important; + } + } + + h1, + h2, + h3, + h4, + h5, + h6, + strong, + b { + font-weight: 500 !important; + } + ol, + ul, + dl { + margin-bottom: 0.5rem; + white-space: inherit; + } + p { + margin-bottom: 0.5rem; + } + hr { + display: none; + } + img { + border-radius: var(--bs-border-radius-lg); + max-height: 40em; + } + th { + min-width: 5em; + } + + .table > :not(caption) > * > * { + padding: 0.3rem; + } + + &:hover { + background-color: rgba(0, 0, 0, 0.03); + .expand-post { + background-image: linear-gradient( + to bottom, + rgba(0, 0, 0, 0), + rgba(247.35, 247.35, 247.35, 1) 25% + ); + } + } + + .post-header { + margin: 4px 0; + } + } + + .post:not(:last-child):before { + content: ""; + position: absolute; + left: 30px; + top: 56px; + bottom: 0; + width: 2px; + background-color: #ddd; + z-index: -1; + } + + .post:not(:first-child):after { + content: ""; + position: absolute; + left: 30px; + top: 0; + width: 2px; + height: 8px; + background-color: #ddd; + z-index: -1; + } + + .left { + margin-right: 12px; + min-width: 40px; + width: 40px; + overflow: hidden; + } + .right { + margin-top: -4px; + flex-grow: 1; + min-width: 0; + } + + .buttons-placeholder { + padding-bottom: 10px; + } + + .buttons { + margin-top: 10px; + margin-bottom: 6px; + column-gap: 4px; + color: #888; + } + + .reposted { + padding-top: 30px; + } +`; + +const contentWidget = ( + + } + src="embeds.near/widget/Post.Content" + props={{ + content, + raw, + truncateContent: props.truncateContent, + noEmbed: props.noEmbed, + }} + /> +); + +return ( + + +
+
+ } + src="/*__@appAccount__*//widget/components.post.post-header" + props={{ + accountId, + blockHeight, + pinned, + hideMenu, + link, + postType: "post", + flagItem: item, + }} + /> + {fullPostLink ? ( + + {contentWidget} + + ) : ( + contentWidget + )} + {props.customButtons ? ( + props.customButtons + ) : !pinned && !hideButtons && blockHeight !== "now" ? ( +
+ State.update({ showReply: !state.showReply }), + }} + /> + + + +
+ ) : ( +
+ )} +
+
+ {state.showReply && ( +
+ State.update({ showReply: false }), + }} + /> +
+ )} + {props.customComments + ? props.customComments + : !props.hideComments && ( +
+ +
+ )} + + +); diff --git a/apps/homepage/widget/create-something.jsx b/apps/builddao/widget/create-something.jsx similarity index 100% rename from apps/homepage/widget/create-something.jsx rename to apps/builddao/widget/create-something.jsx diff --git a/apps/homepage/widget/home.jsx b/apps/builddao/widget/home.jsx similarity index 100% rename from apps/homepage/widget/home.jsx rename to apps/builddao/widget/home.jsx diff --git a/apps/homepage/widget/login.jsx b/apps/builddao/widget/login.jsx similarity index 100% rename from apps/homepage/widget/login.jsx rename to apps/builddao/widget/login.jsx diff --git a/apps/homepage/widget/propose-widget.jsx b/apps/builddao/widget/propose-widget.jsx similarity index 100% rename from apps/homepage/widget/propose-widget.jsx rename to apps/builddao/widget/propose-widget.jsx diff --git a/apps/homepage/widget/propose.jsx b/apps/builddao/widget/propose.jsx similarity index 100% rename from apps/homepage/widget/propose.jsx rename to apps/builddao/widget/propose.jsx diff --git a/apps/homepage/widget/section/cta.jsx b/apps/builddao/widget/section/cta.jsx similarity index 100% rename from apps/homepage/widget/section/cta.jsx rename to apps/builddao/widget/section/cta.jsx diff --git a/apps/homepage/widget/section/footer.jsx b/apps/builddao/widget/section/footer.jsx similarity index 100% rename from apps/homepage/widget/section/footer.jsx rename to apps/builddao/widget/section/footer.jsx diff --git a/apps/homepage/widget/section/goals.jsx b/apps/builddao/widget/section/goals.jsx similarity index 100% rename from apps/homepage/widget/section/goals.jsx rename to apps/builddao/widget/section/goals.jsx diff --git a/apps/homepage/widget/section/governance.jsx b/apps/builddao/widget/section/governance.jsx similarity index 100% rename from apps/homepage/widget/section/governance.jsx rename to apps/builddao/widget/section/governance.jsx diff --git a/apps/homepage/widget/section/hero.jsx b/apps/builddao/widget/section/hero.jsx similarity index 100% rename from apps/homepage/widget/section/hero.jsx rename to apps/builddao/widget/section/hero.jsx diff --git a/apps/homepage/widget/section/join.jsx b/apps/builddao/widget/section/join.jsx similarity index 100% rename from apps/homepage/widget/section/join.jsx rename to apps/builddao/widget/section/join.jsx diff --git a/apps/embeds/bos.config.json b/apps/embeds/bos.config.json new file mode 100644 index 00000000..50143cc0 --- /dev/null +++ b/apps/embeds/bos.config.json @@ -0,0 +1,3 @@ +{ + "appAccount": "embeds.near" +} \ No newline at end of file diff --git a/apps/embeds/widget/Embed.jsx b/apps/embeds/widget/Embed.jsx new file mode 100644 index 00000000..7cb6a601 --- /dev/null +++ b/apps/embeds/widget/Embed.jsx @@ -0,0 +1,164 @@ +const Wrapper = styled.div` + border-radius: 0.5em; + width: 100%; + white-space: normal; + margin-top: 12px; +`; + +const accountId = context.accountId; + +// Default Embeds +const EmbedMap = new Map([ + [ + "mob.near/widget/MainPage.N.Post.Page", + "mob.near/widget/MainPage.N.Post.Embed", + ], + [ + "mob.near/widget/MainPage.N.Post.Embed", + "mob.near/widget/MainPage.N.Post.Embed", + ], +]); + +if (accountId) { + const installedEmbeds = JSON.parse( + Social.get(`${accountId}/settings/every/embed`, "final") || "null" + ); + + if (installedEmbeds) { + installedEmbeds.forEach((embed) => { + EmbedMap.set(embed.widgetSrc, embed.embedSrc); + }); + } +} + +const href = props.href; + +const assertString = s => { + if (typeof s !== "string") { + return null; + } +} + +// checks for use of "**" in widgetSrc string +const containsGlob = path => /\*\*/.test(path) + +const findWithKey = (key, href) => { + let fragments = key.split('**').filter(f => f != '') + const hasFragment = (str, fragment) => str.search(fragment) != -1 + while (fragments.length > 0) { + if (hasFragment(href, fragments[0])) { + fragments.shift() + } else { + return null + } + } + return true +} + +const parseUrl = (url) => { + assertString(url) + if (url.startsWith("/")) { + url = `https://near.social${url}`; + } + try { + return new URL(url); + } catch { + return null; + } +}; + +const parseGlob = (path) => { + assertString(path) + const keysWithGlobs = [...EmbedMap.keys()].filter(key => containsGlob(key)) + const keysThatMatch = keysWithGlobs.filter(key => findWithKey(key,href)) + if (keysThatMatch.length >= 1) { + try { + return keysThatMatch[0] + } catch { + return null + } + } + return null +}; + +const parsed = useMemo(() => { + // try parsing embed link to URL + const url = parseUrl(href); + if (!!url) { + return { + widgetSrc: url.pathname.substring(1), + props: Object.fromEntries([...url.searchParams.entries()]), + }; + } + + // try parsing embed link to glob if url failed + const widgetSrc = parseGlob(href) + if (!!widgetSrc) { + return { + widgetSrc, + props: { + href, + }, + }; + } + + // neither valid url nor valid glob + return null; +}, [href]); + +function filterByWidgetSrc(obj, widgetSrcValue) { + let result = []; + + function recurse(currentObj) { + if (typeof currentObj === "object" && currentObj !== null) { + if ( + currentObj.metadata && + currentObj.metadata.widgetSrc === widgetSrcValue + ) { + result.push(currentObj); + } + Object.values(currentObj).forEach((value) => recurse(value)); + } + } + + recurse(obj); + return result; +} + +if (!parsed || !EmbedMap.has(parsed.widgetSrc)) { + return ( + +
+
+

You do not have a plugin installed to render this embedding.

+ + Install one from the marketplace + → + +
+ + {`or `} + + click here + + {` to view`} + +
+
+
+
+ ); +} + +const widgetSrc = EmbedMap.get(parsed.widgetSrc); +return ( + + + +); diff --git a/apps/embeds/widget/EmbedPlugin.jsx b/apps/embeds/widget/EmbedPlugin.jsx new file mode 100644 index 00000000..279380a5 --- /dev/null +++ b/apps/embeds/widget/EmbedPlugin.jsx @@ -0,0 +1,181 @@ +const { accountId, name, type, metadata, plugin } = props; + +const installedEmbeds = JSON.parse( + Social.get(`${context.accountId}/settings/every/embed`, "final") || "null" +); + +console.log("installedEmbeds", installedEmbeds); + +if (plugin) { + return ( +
+
+

widgetSrc: {plugin.widgetSrc}

+

embedSrc: {plugin.embedSrc}

+
+ {context.accountId && ( +
+ + + +
+ )} +
+ ); +} +const data = JSON.parse( + Social.get(`${accountId}/plugin/embed/${name}`, "final") || "null" +); + +if (!data) { + return

Loading... {`${accountId}/plugin/embed/${name}`}

; +} + +// Use metadata.name if it exists, otherwise use the passed name +const displayName = metadata.name || name; +const defaultImage = + "https://ipfs.near.social/ipfs/bafkreihi3qh72njb3ejg7t2mbxuho2vk447kzkvpjtmulsb2njd6m2cfgi"; + +return ( +
+
+ +
+ +
+ {accountId}/{displayName} +
+ + {metadata.description && ( +

+ {metadata.description} +

+ )} + {data && ( + <> +

widgetSrc: {data.widgetSrc}

+

embedSrc: {data.embedSrc}

+ + )} +
+ {context.accountId && ( +
+ + + +
+ )} +
+); diff --git a/apps/embeds/widget/Feed.jsx b/apps/embeds/widget/Feed.jsx new file mode 100644 index 00000000..d5ffdabc --- /dev/null +++ b/apps/embeds/widget/Feed.jsx @@ -0,0 +1,27 @@ +const { Feed } = VM.require("devs.near/widget/Module.Feed"); + +Feed = Feed || (() => <>); // make sure you have this or else it can break + +return ( + { + return ( + } + props={{ accountId: p.accountId, blockHeight: p.blockHeight }} + /> + ); + }} + /> +); diff --git a/apps/embeds/widget/Plugin/Creator.jsx b/apps/embeds/widget/Plugin/Creator.jsx new file mode 100644 index 00000000..0408020d --- /dev/null +++ b/apps/embeds/widget/Plugin/Creator.jsx @@ -0,0 +1,141 @@ +/** + * TODO: This should be more generalized. + * Currently only supports embed plugins + */ +const Wrapper = styled.div` + max-width: 400px; + margin: 0 auto; +`; + +const TabContent = styled.div` + margin-top: 1rem; +`; + +const Form = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + width: 100%; +`; + +const Label = styled.label` + font-weight: bold; +`; + +const Input = styled.input` + padding: 5px; +`; + +const Select = styled.select` + padding: 8px; +`; + +const FormGroup = styled.div` + display: flex; + flex-direction: column; +`; + +const [name, setName] = useState(""); +const [description, setDescription] = useState(""); +const [widgetSrc, setWidgetSrc] = useState(""); +const [embedSrc, setEmbedSrc] = useState(""); +const [activeTab, setActiveTab] = useState("data"); + +function generateUID() { + const maxHex = 0xffffffff; + const randomNumber = Math.floor(Math.random() * maxHex); + return randomNumber.toString(16).padStart(8, '0'); +} + + +const handleCreate = () => { + Social.set({ + plugin: { + embed: { + [generateUID()]: { + "": JSON.stringify({ widgetSrc, embedSrc }), + metadata: { + name, + description, + widgetSrc, // TODO: Hack. + }, + }, + }, + }, + }); +}; + +return ( + +

create embed plugin

+ + + + {activeTab === "data" && ( +
+ + + setWidgetSrc(e.target.value)} + /> + + + + setEmbedSrc(e.target.value)} + /> + +
+ )} +
+ + {activeTab === "metadata" && ( +
+ + + setName(e.target.value)} + /> + + + +