Browse Source

fix: updated changelog

pull/9323/head
DarkPhoenix2704 2 months ago
parent
commit
8587703e78
  1. 255
      packages/nc-gui/components/feed/Changelog/Item.vue
  2. 4
      packages/nc-gui/components/feed/Changelog/index.vue
  3. 18
      packages/nc-gui/components/feed/Recents/Card.vue
  4. 2
      packages/nc-gui/components/feed/View.vue
  5. 183
      packages/nc-gui/components/nc/Popover.vue
  6. 5
      packages/nc-gui/plugins/animation.ts

255
packages/nc-gui/components/feed/Changelog/Item.vue

@ -9,14 +9,13 @@ import type { ProductFeedItem } from '../../../lib/types'
const props = defineProps<{
item: ProductFeedItem
index: number
}>()
const {
item: { 'Published Time': CreatedAt, Description, Title, Tags },
item: { 'Published Time': CreatedAt, Description, Title, Tags, Images },
} = props
const truncate = ref(true)
const iconColorMap = {
'Hotfix': {
icon: iconMap.ncTool,
@ -32,94 +31,112 @@ const iconColorMap = {
},
}
const tags = computed(() => {
return Tags?.split(',').map((tag) => ({
const tags = computed(() => [
...(props.index === 0
? [
{
text: 'Latest Release',
color: 'purple',
},
]
: []),
...(Tags?.split(',').map((tag) => ({
text: tag,
href: `/tags/${tag}`,
...(iconColorMap[tag as any] || {}),
}))
})
...(iconColorMap[tag] || {}),
})) || []),
])
const { getPossibleAttachmentSrc } = useAttachment()
const renderMarkdown = async (markdown: string) => {
return await unified().use(remarkParse).use(remarkRehype).use(rehypeSanitize).use(rehypeStringify).process(markdown)
}
const truncate = ref(true)
const renderedText = computedAsync(async () => {
return await renderMarkdown(truncate.value ? Description.substring(0, 300).concat('...') : Description)
return await renderMarkdown(
truncate.value
? Description.replace(/[*_~]|\[.*?\]|<\/?[^>]+(>|$)/g, '')
.replace(/\(https?:\/\/[^\s)]+\)\]\(https?:\/\/[^\s)]+\)/g, '')
.replace(/^(\*\*)?#?\s*(\p{Emoji})\s*NocoDB\s*v[\d.]+(\s*-\s*|\*\*$)/u, '# ')
.replace(/(!?\(https?:\/\/[^\s)]+\)(?:\]\(https?:\/\/[^\s)]+(?:\s+"[^"]*")?\))?)/g, '')
.replace('-', '')
.substring(0, 100)
.concat('...')
: Description.replace(/^\[!\[.*?\]\(https?:\/\/.*?\)\]\(https?:\/\/.*?\)/m, '').replace(
/^(\*\*)?#?\s*(\p{Emoji})\s*NocoDB\s*v[\d.]+(\s*-\s*|\*\*$)/u,
'# ',
),
)
})
</script>
<template>
<div class="block relative">
<div class="vertical-line"></div>
<div class="relative border-1 border-nc-gray-medium rounded-xl pb-6 changelog-item bg-white">
<div class="aside">
<div class="aside-divider">
<div class="aside-divider-dot mt-8"></div>
</div>
<div class="aside-inner">
<div class="text-sm text-right pr-8 text-gray-700 leading-5">
{{ dayjs(CreatedAt).format('MMMM D, YYYY') }}
</div>
</div>
</div>
<div class="content">
<div class="pt-6 pr-6 space-y-5">
<div class="flex items-center">
<a
v-for="tag in tags"
:key="tag.text"
:class="{
'bg-red-50': tag.color === 'red',
'bg-purple-50': tag.color === 'purple',
'bg-green-50': tag.color === 'green',
}"
:href="tag.href"
class="mr-3 flex gap-2 items-center px-1 rounded-md"
>
<component
:is="tag.icon"
:class="{
'fill-red-700 text-transparent': tag.color === 'red',
'fill-purple-700 text-transparent': tag.color === 'purple',
'fill-green-700 text-transparent': tag.color === 'green',
}"
class="w-4 h-4"
/>
<span
:class="{
'text-red-500': tag.color === 'red',
'text-purple-500': tag.color === 'purple',
'text-green-700': tag.color === 'green',
}"
class="leading-5 text-[13px]"
>
{{ tag.text }}
</span>
</a>
</div>
<div class="flex flex-col gap-2">
<NcBadge
:border="false"
color="brand"
class="font-semibold text-[13px] nc-title-badge cursor-pointer"
@click="openLink(item.Url)"
>
{{ Title }}
</NcBadge>
<div class="prose max-w-none" v-html="renderedText"></div>
<NcButton v-if="truncate" size="small" class="w-29" type="text" @click="truncate = false">
<div class="gap-2 flex items-center">
Show more
<GeneralIcon icon="arrowDown" />
</div>
</NcButton>
</div>
</div>
<div class="relative rounded-xl flex flex-col mt-8 bg-white changelog-card">
<div
class="w-full relative border border-black h-[334px] xl:h-[394px] w-[540px] xl:w-[640px] border-opacity-10 rounded-t-xl overflow-hidden"
>
<LazyCellAttachmentPreviewImage
:srcs="getPossibleAttachmentSrc(Images[0] ?? [])"
class="absolute w-full h-full inset-0 object-cover transition-all ease-in-out transform hover:scale-105"
/>
</div>
<div class="flex my-4 px-4 items-center justify-between">
<div class="flex items-center">
<NcBadge
:border="false"
color="brand"
class="font-semibold text-[13px] mr-3 nc-title-badge cursor-pointer"
@click="openLink(item.Url)"
>
{{ Title }}
</NcBadge>
<a
v-for="tag in tags"
:key="tag.text"
:class="{
'bg-red-50': tag.color === 'red',
'bg-purple-50': tag.color === 'purple',
'bg-green-50': tag.color === 'green',
}"
:href="tag.href"
class="mr-3 flex gap-2 items-center px-1 rounded-md"
>
<component
:is="tag.icon"
:class="{
'fill-red-700 text-transparent': tag.color === 'red',
'fill-purple-700 text-transparent': tag.color === 'purple',
'fill-green-700 text-transparent': tag.color === 'green',
}"
class="w-4 h-4"
/>
<span
:class="{
'text-red-500': tag.color === 'red',
'text-purple-500': tag.color === 'purple',
'text-green-700': tag.color === 'green',
}"
class="leading-5 text-[13px]"
>
{{ tag.text }}
</span>
</a>
</div>
<span class="font-medium text-sm text-gray-500">
{{ dayjs(CreatedAt).format('MMM DD, YYYY') }}
</span>
</div>
<div class="flex flex-1 px-4 pb-3 justify-between flex-col gap-2">
<div class="prose max-w-none" v-html="renderedText"></div>
</div>
<NcButton v-if="truncate" size="small" class="w-29 mx-4 mb-3" type="text" @click="truncate = false">
<div class="gap-2 flex items-center">
Show more
<GeneralIcon icon="arrowDown" />
</div>
</NcButton>
</div>
</template>
@ -128,75 +145,43 @@ const renderedText = computedAsync(async () => {
width: fit-content;
}
.vertical-line {
@apply ml-47.5;
height: 24px;
width: 2px;
background-color: #e7e7e9;
.changelog-card {
@apply transform transition-all ease-in-out;
&:hover {
box-shadow: 0px 4px 8px -2px rgba(0, 0, 0, 0.08), 0px 2px 4px -2px rgba(0, 0, 0, 0.04);
}
}
.changelog-item {
box-shadow: 0px 2px 4px -2px rgba(51, 102, 255, 0.08), 0px 4px 4px -2px rgba(51, 102, 255, 0.04);
a {
@apply !no-underline;
}
.content {
@apply !pl-52;
:deep(.prose) {
@apply !max-w-auto;
a {
@apply !no-underline;
@apply text-gray-900;
}
:deep(.prose) {
a {
@apply text-gray-900;
}
h1 {
@apply text-2xl text-nc-content-gray-emphasis leading-9 mb-0;
font-weight: 700;
}
p {
@apply text-nc-content-gray-emphasis leading-6;
font-size: 14px !important;
}
li {
@apply text-nc-content-gray-emphasis leading-6;
font-size: 14px !important;
}
h3 {
@apply text-nc-content-gray-emphasis text-lg leading-5 mb-0;
}
h1 {
@apply text-3xl text-nc-content-gray-emphasis leading-9 mb-0;
font-weight: 700;
}
}
.aside {
@apply absolute left-0 top-0 bottom-0 w-52;
h2 {
@apply text-nc-content-gray-emphasis text-xl leading-6 mb-0;
}
p {
@apply text-nc-content-gray-emphasis leading-6;
font-size: 14px !important;
}
.aside-inner {
@apply sticky top-0 pt-5 pb-4;
li {
@apply text-nc-content-gray-emphasis leading-6;
font-size: 14px !important;
}
.aside-divider {
@apply absolute top-0 right-0 bottom-0 w-1.5 mr-3;
&:before {
@apply absolute bg-[#E7E7E9] left-0 transform -translate-x-1/2;
content: '';
top: 0px;
bottom: 0px;
width: 2px;
border-radius: 2px;
}
.aside-divider-dot {
@apply sticky h-5 mr-3 top-9;
transform: translateY(calc(-50% + 3px)) translateX(50%);
&:before {
@apply bg-brand-500 absolute w-2 h-2 border-2 border-white left-0 rounded-full transform -translate-x-1/2;
content: '';
}
}
h3 {
@apply text-nc-content-gray-emphasis text-lg leading-6 mb-0;
}
}
</style>

4
packages/nc-gui/components/feed/Changelog/index.vue

@ -31,8 +31,8 @@ const { isLoading } = useInfiniteScroll(
<GeneralLoader size="xlarge" />
</div>
<div v-else class="w-[47.75rem] mx-auto">
<FeedChangelogItem v-for="feed in githubFeed" :key="feed.Id" :item="feed" />
<div v-else class="mx-auto my-8 max-w-[540px] xl:max-w-[640px] justify-around justify-items-center">
<FeedChangelogItem v-for="(feed, index) in githubFeed" :key="feed.Id" :item="feed" :index="index" />
</div>
</div>
</template>

18
packages/nc-gui/components/feed/Recents/Card.vue

@ -37,7 +37,7 @@ const renderedText = computedAsync(async () => {
? Description.replace(/!\[.*?\]\(.*?\)/g, '')
.substring(0, 250)
.concat('...')
: Description,
: Description.replace(/^\[!\[.*?\]\(https?:\/\/.*?\)\]\(https?:\/\/.*?\)/m, ''),
)
})
@ -103,18 +103,26 @@ const { width } = useWindowSize()
a {
@apply text-gray-900;
}
h1 {
@apply text-2xl text-nc-content-gray-emphasis truncate leading-9 mb-0;
@apply text-3xl text-nc-content-gray-emphasis truncate leading-9 mb-0;
font-weight: 700;
}
h2 {
@apply text-nc-content-gray-emphasis text-xl leading-6 !mb-0;
}
p {
@apply text-nc-content-gray-emphasis leading-5;
@apply text-nc-content-gray-emphasis leading-6;
font-size: 14px !important;
}
li {
@apply text-nc-content-gray-emphasis leading-6;
font-size: 14px !important;
}
h3 {
@apply text-nc-content-gray-emphasis text-lg leading-5 mb-0;
@apply text-nc-content-gray-emphasis text-lg leading-6 mb-0;
}
}
}

2
packages/nc-gui/components/feed/View.vue

@ -96,7 +96,7 @@ const tabs: Array<{
}
.changelog-left {
left: calc(50% + 400px);
@apply xl:left-[calc(50%+350px)] left-[calc(50%+300px)];
}
.changelog-twitter {

183
packages/nc-gui/components/nc/Popover.vue

@ -0,0 +1,183 @@
<script setup>
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
const props = defineProps({
modelValue: {
type: Boolean,
default: false,
},
placement: {
type: String,
default: 'center',
validator: (value) => ['top', 'bottom', 'left', 'right', 'center'].includes(value),
},
offset: {
type: Array,
default: () => [0, 10],
},
width: {
type: String,
default: 'auto',
},
zIndex: {
type: Number,
default: 1000,
},
closeOnClickOutside: {
type: Boolean,
default: true,
},
closeOnEsc: {
type: Boolean,
default: true,
},
transition: {
type: String,
default: 'fade',
},
})
const emit = defineEmits(['update:modelValue'])
const triggerRef = ref(null)
const popoverRef = ref(null)
const isOpen = ref(props.modelValue)
const popoverStyle = computed(() => ({
position: 'fixed',
zIndex: props.zIndex,
width: props.width,
}))
const updatePopoverPosition = () => {
if (!triggerRef.value || !popoverRef.value) return
const triggerRect = triggerRef.value.getBoundingClientRect()
const popoverRect = popoverRef.value.getBoundingClientRect()
let top, left
switch (props.placement) {
case 'top':
top = triggerRect.top - popoverRect.height - props.offset[1]
left = triggerRect.left + (triggerRect.width - popoverRect.width) / 2 + props.offset[0]
break
case 'bottom':
top = triggerRect.bottom + props.offset[1]
left = triggerRect.left + (triggerRect.width - popoverRect.width) / 2 + props.offset[0]
break
case 'left':
top = triggerRect.top + (triggerRect.height - popoverRect.height) / 2 + props.offset[1]
left = triggerRect.left - popoverRect.width - props.offset[0]
break
case 'right':
top = triggerRect.top + (triggerRect.height - popoverRect.height) / 2 + props.offset[1]
left = triggerRect.right + props.offset[0]
break
case 'center':
top = triggerRect.top + (triggerRect.height - popoverRect.height) / 2 + props.offset[1]
left = triggerRect.left + (triggerRect.width - popoverRect.width) / 2 + props.offset[0]
break
}
// Ensure the popover stays within the viewport
const viewportWidth = window.innerWidth
const viewportHeight = window.innerHeight
top = Math.max(0, Math.min(top, viewportHeight - popoverRect.height))
left = Math.max(0, Math.min(left, viewportWidth - popoverRect.width))
Object.assign(popoverRef.value.style, {
top: `${top}px`,
left: `${left}px`,
})
}
const openPopover = () => {
isOpen.value = true
emit('update:modelValue', true)
nextTick(updatePopoverPosition)
}
const closePopover = () => {
isOpen.value = false
emit('update:modelValue', false)
}
const handleClickOutside = (event) => {
if (
props.closeOnClickOutside &&
popoverRef.value &&
!popoverRef.value.contains(event.target) &&
!triggerRef.value.contains(event.target)
) {
closePopover()
}
}
const handleKeyDown = (event) => {
if (props.closeOnEsc && event.key === 'Escape') {
closePopover()
}
}
onMounted(() => {
if (props.closeOnClickOutside) document.addEventListener('mousedown', handleClickOutside)
if (props.closeOnEsc) document.addEventListener('keydown', handleKeyDown)
window.addEventListener('resize', updatePopoverPosition)
window.addEventListener('scroll', updatePopoverPosition)
})
onUnmounted(() => {
if (props.closeOnClickOutside) document.removeEventListener('mousedown', handleClickOutside)
if (props.closeOnEsc) document.removeEventListener('keydown', handleKeyDown)
window.removeEventListener('resize', updatePopoverPosition)
window.removeEventListener('scroll', updatePopoverPosition)
})
watch(
() => props.modelValue,
(newValue) => {
isOpen.value = newValue
if (newValue) nextTick(updatePopoverPosition)
},
)
</script>
<template>
<div>
<div ref="triggerRef" @click="openPopover">
<slot name="trigger" :open="openPopover" :close="closePopover" :is-open="isOpen">
<button>Open Popover</button>
</slot>
</div>
<Teleport to="body">
<Transition :name="transition">
<div v-if="isOpen" ref="popoverRef" :style="popoverStyle" class="popover-content">
<slot name="content" :close="closePopover">
<div class="p-4">
<p>Default popover content</p>
<button @click="closePopover">Close</button>
</div>
</slot>
</div>
</Transition>
</Teleport>
</div>
</template>
<style scoped lang="scss">
.popover-content {
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

5
packages/nc-gui/plugins/animation.ts

@ -0,0 +1,5 @@
import { MotionPlugin } from '@vueuse/motion'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(MotionPlugin)
})
Loading…
Cancel
Save