Skip to content

Commit

Permalink
Merge pull request #480 from dev-protocol/feature/create-votes-page
Browse files Browse the repository at this point in the history
投票一覧ページ
  • Loading branch information
kazuyoshi80 authored May 23, 2024
2 parents abb63dd + bc7b5f9 commit 3d73646
Show file tree
Hide file tree
Showing 8 changed files with 285 additions and 6,570 deletions.
16 changes: 15 additions & 1 deletion .preview/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,21 @@ export default () =>
key: 'feeds',
value: [
{
id: 'default-2',
id: 'default-2-id',
title: 'default-2-title',
slugs: 'default-2-slug',
database: {
type: 'documents:redis',
key: uuidv5(
toUtf8Bytes('default-2'),
uuidv5('EXAMPLE_NAMESPACE', uuidv5.URL),
), // > posts::694666bb-b2ec-542b-a5d6-65b470e5c494
},
},
{
id: 'default-3',
title: '',
slugs: 'default-3-slug',
database: {
type: 'documents:redis',
key: uuidv5(
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@devprotocol/clubs-plugin-posts-voting",
"version": "0.6.1",
"version": "0.7.0",
"type": "module",
"description": "Template repository for using TypeScript",
"main": "dist/index.js",
Expand Down Expand Up @@ -40,7 +40,10 @@
"ethers": "6.12.1",
"ramda": "0.30.0",
"sass": "1.77.2",
"uuid": "^9.0.1"
"uuid": "^9.0.1",
"@boringer-avatars/vue3": "^0.2.1",
"remark": "^15.0.1",
"strip-markdown": "^6.0.0"
},
"resolutions": {
"@devprotocol/util-ts": "4.0.0"
Expand Down
24 changes: 24 additions & 0 deletions src/Pages/Votes.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
import { Option } from '../types'
import { ClubsPropsPages, Membership } from '@devprotocol/clubs-core'
import Votes from '../components/Pages/Votes.vue'
import type { OptionsDatabase } from '@devprotocol/clubs-plugin-posts'
interface Props extends ClubsPropsPages {
options: Option[]
propertyAddress: string
adminRolePoints: number
rpcUrl: string
feeds: OptionsDatabase[]
postsPluginId: string
}
const { options, feeds, postsPluginId } = Astro.props
---

<Votes
client:load
options={options}
feeds={feeds}
postsPluginId={postsPluginId}
/>
Binary file added src/assets/images/icon-lock.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
131 changes: 131 additions & 0 deletions src/components/Pages/Votes.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<script lang="ts" setup>
import type { Option } from '../../types.ts'
import type { OptionsDatabase, Posts } from '@devprotocol/clubs-plugin-posts'
import { type ClubsProfile, decode } from '@devprotocol/clubs-core'
import { onMounted, ref } from 'vue'
import Profile from '../Votes/Profile.vue'
import IconLock from '../../assets/images/icon-lock.png'
import { remark } from 'remark'
import strip from 'strip-markdown'
type Props = {
options: Option[]
feeds: OptionsDatabase[]
postsPluginId: string
}
const props = defineProps<Props>()
const polls = ref<any[]>([])
const { postsPluginId, feeds } = props
type Polls = {
title: string
values: PostsPlus[]
}
type PostsPlus = Posts & {
image: string
profile: Promise<{
readonly profile: ClubsProfile | undefined
readonly error: Error | undefined
}>
stripedMarkdown: string
}
const feedId = ref('')
const fetchPolls = async (feed: OptionsDatabase): Promise<Polls> => {
feedId.value = feed.id
const title = feed.title
const url = new URL(
`/api/${postsPluginId}/${feedId.value}/search/has:option/%23poll`,
window.location.origin,
)
const res = await fetch(url.toString())
const json = await res.json()
return {
title: title ? title : feedId.value,
values: decode(json.contents),
}
}
onMounted(async () => {
await Promise.all(
feeds.map(async (feed) => {
const data = await fetchPolls(feed)
data.values = data.values.map((post) => {
post.updated_at = new Date(post.updated_at).toLocaleString('ja-JP')
const images = post.options.find((item) => item.key === '#images')
if (images && images.value.length > 0) {
post.image = images.value[0]
}
remark()
.use(strip)
.process(post.content)
.then((text) => {
post.stripedMarkdown = text.toString()
})
return post
})
polls.value = [...polls.value, data]
}),
)
})
</script>

<template>
<div class="p-4">
<div v-for="poll in polls" :key="poll.title">
<h2 class="mb-4 text-xl">{{ poll.title }}</h2>
<a
v-for="post in poll.values"
:key="post.id"
:href="`/posts/${feedId}/${post.id}`"
class="block mb-4 p-2 bg-gray-100 rounded"
>
<div class="flex justify-between gap-2">
<div class="w-full">
<p class="text-lg font-bold">Post_title: {{ post.title }}</p>
<p class="mb-1 text-xs text-gray-400">{{ post.updated_at }}</p>
<div class="flex justify-between gap-2">
<Profile :address="post.created_by" />
<p v-if="true" class="flex-grow flex-wrap text-lg truncate">
{{ post.stripedMarkdown }}
</p>
<div
v-else
class="flex flex-col justify-center items-center flex-grow p-2 bg-gray-200 rounded"
>
<img
class="mb-1 w-5"
:src="IconLock.src"
alt="paper-airplane"
/>
<p class="leading-none">Locked</p>
</div>
</div>
</div>
<figure v-if="!isMasked && post.image">
<img
:src="post.image"
class="rounded max-w-20 max-h-20 object-cover object-center"
alt="post image"
/>
</figure>
</div>
</a>
<div v-if="poll.length < 1" class="mb-4 p-2 bg-gray-100 rounded">
<p class="w-full text-gray-400 text-center">Empty :)</p>
</div>
</div>
</div>
</template>
74 changes: 74 additions & 0 deletions src/components/Votes/Profile.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { ZeroAddress } from 'ethers'
import { Avatar } from '@boringer-avatars/vue3'
import { fetchProfile } from '@devprotocol/clubs-core'
type Props = {
address: string
}
const props = defineProps<Props>()
const avatar = ref('')
const name = ref('')
onMounted(() => {
if (!props.address || props.address === ZeroAddress) {
name.value = truncateEthAddress(props.address)
return
}
// fetch profile
getProfile(props.address)
})
const truncateEthAddress = (address: string) => {
const match = address.match(
/^(0x[a-zA-Z0-9]{4})[a-zA-Z0-9]+([a-zA-Z0-9]{4})$/,
)
if (!match) return address
return `${match[1]}\u2026${match[2]}`
}
const getProfile = async (address: string) => {
const res = await fetchProfile(address)
if (res?.error) {
console.error(res.error)
return
}
avatar.value = res?.profile?.avatar || ''
name.value = res?.profile?.username ?? truncateEthAddress(address)
}
</script>

<template>
<div class="">
<template v-if="avatar">
<div
class="h-8 w-8 rounded-full bg-cover bg-center bg-no-repeat"
:style="`background-image: url(${avatar})`"
/>
</template>
<template v-else>
<Avatar
class=""
:title="false"
:size="32"
variant="beam"
:name="props.address"
:square="false"
/>
</template>
</div>
</template>

<style scoped>
.posts-username {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
</style>
37 changes: 36 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type {
ClubsFunctionGetApiPaths,
ClubsFunctionGetPagePaths,
ClubsFunctionGetSlots,
ClubsFunctionPlugin,
ClubsPluginMeta,
} from '@devprotocol/clubs-core'
import { ClubsPluginCategory } from '@devprotocol/clubs-core'
import { SlotName } from '@devprotocol/clubs-plugin-posts'
import { type OptionsDatabase, SlotName } from '@devprotocol/clubs-plugin-posts'
import { votingHandler } from './ApiHandler'
import Icon from './assets/images/Voting.png'
import Preview1 from './assets/images/voting-preview01.png'
Expand All @@ -14,6 +15,8 @@ import Preview3 from './assets/images/voting-preview03.png'
import Readme from './readme.astro'
import AfterContentForm from './components/edit-after-content-form.astro'
import AfterPostContent from './components/feed-after-post-content.astro'
import type { UndefinedOr } from '@devprotocol/util-ts'
import Votes from './Pages/Votes.astro'

export const getSlots = (async () => {
return [
Expand Down Expand Up @@ -50,8 +53,40 @@ export const getApiPaths = (async () => {
]
}) satisfies ClubsFunctionGetApiPaths

const getPagePaths = (async (
options,
{ propertyAddress, adminRolePoints, rpcUrl },
{ getPluginConfigById },
) => {
const [postsPlugin] = getPluginConfigById('devprotocol:clubs:plugin:posts')

const feeds = postsPlugin?.options?.find(
({ key }: Readonly<{ key: string }>) => key === 'feeds',
)?.value as UndefinedOr<readonly OptionsDatabase[]>

const props = {
options,
propertyAddress,
adminRolePoints,
rpcUrl,
feeds,
postsPluginId: postsPlugin?.id,
}

return [
{
paths: ['votes'],
component: Votes,
props: {
...props,
},
},
]
}) satisfies ClubsFunctionGetPagePaths

export default {
getSlots,
meta,
getApiPaths,
getPagePaths,
} satisfies ClubsFunctionPlugin
Loading

0 comments on commit 3d73646

Please sign in to comment.