feat(client): add rss-marquee widget
This commit is contained in:
		
							parent
							
								
									bbdc52a7ea
								
							
						
					
					
						commit
						6ba888f476
					
				
					 7 changed files with 141 additions and 8 deletions
				
			
		|  | @ -17,6 +17,7 @@ You should also include the user name that made the change. | ||||||
| - Client: Improve control panel @syuilo | - Client: Improve control panel @syuilo | ||||||
| - Client: Show warning in control panel when there is an unresolved abuse report @syuilo | - Client: Show warning in control panel when there is an unresolved abuse report @syuilo | ||||||
| - Client: Add instance-cloud widget @syuilo | - Client: Add instance-cloud widget @syuilo | ||||||
|  | - Client: Add rss-marquee widget @syuilo | ||||||
| - Make possible to delete an account by admin @syuilo | - Make possible to delete an account by admin @syuilo | ||||||
| - Improve player detection in URL preview @mei23 | - Improve player detection in URL preview @mei23 | ||||||
| - Add Badge Image to Push Notification #8012 @tamaina | - Add Badge Image to Push Notification #8012 @tamaina | ||||||
|  |  | ||||||
|  | @ -1246,6 +1246,7 @@ _widgets: | ||||||
|   trends: "トレンド" |   trends: "トレンド" | ||||||
|   clock: "時計" |   clock: "時計" | ||||||
|   rss: "RSSリーダー" |   rss: "RSSリーダー" | ||||||
|  |   rssMarquee: "RSSリーダー(マーキー)" | ||||||
|   activity: "アクティビティ" |   activity: "アクティビティ" | ||||||
|   photos: "フォト" |   photos: "フォト" | ||||||
|   digitalClock: "デジタル時計" |   digitalClock: "デジタル時計" | ||||||
|  |  | ||||||
|  | @ -76,6 +76,7 @@ | ||||||
| 		"vanilla-tilt": "1.7.2", | 		"vanilla-tilt": "1.7.2", | ||||||
| 		"vite": "3.0.0-beta.5", | 		"vite": "3.0.0-beta.5", | ||||||
| 		"vue": "3.2.37", | 		"vue": "3.2.37", | ||||||
|  | 		"vue-marquee-text-component": "2.0.1", | ||||||
| 		"vue-prism-editor": "2.0.0-alpha.2", | 		"vue-prism-editor": "2.0.0-alpha.2", | ||||||
| 		"vuedraggable": "4.0.1", | 		"vuedraggable": "4.0.1", | ||||||
| 		"websocket": "1.0.34", | 		"websocket": "1.0.34", | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ export default function(app: App) { | ||||||
| 	app.component('MkwTimeline', defineAsyncComponent(() => import('./timeline.vue'))); | 	app.component('MkwTimeline', defineAsyncComponent(() => import('./timeline.vue'))); | ||||||
| 	app.component('MkwCalendar', defineAsyncComponent(() => import('./calendar.vue'))); | 	app.component('MkwCalendar', defineAsyncComponent(() => import('./calendar.vue'))); | ||||||
| 	app.component('MkwRss', defineAsyncComponent(() => import('./rss.vue'))); | 	app.component('MkwRss', defineAsyncComponent(() => import('./rss.vue'))); | ||||||
|  | 	app.component('MkwRssMarquee', defineAsyncComponent(() => import('./rss-marquee.vue'))); | ||||||
| 	app.component('MkwTrends', defineAsyncComponent(() => import('./trends.vue'))); | 	app.component('MkwTrends', defineAsyncComponent(() => import('./trends.vue'))); | ||||||
| 	app.component('MkwClock', defineAsyncComponent(() => import('./clock.vue'))); | 	app.component('MkwClock', defineAsyncComponent(() => import('./clock.vue'))); | ||||||
| 	app.component('MkwActivity', defineAsyncComponent(() => import('./activity.vue'))); | 	app.component('MkwActivity', defineAsyncComponent(() => import('./activity.vue'))); | ||||||
|  | @ -29,13 +30,14 @@ export const widgets = [ | ||||||
| 	'timeline', | 	'timeline', | ||||||
| 	'calendar', | 	'calendar', | ||||||
| 	'rss', | 	'rss', | ||||||
|  | 	'rssMarquee', | ||||||
| 	'trends', | 	'trends', | ||||||
| 	'clock', | 	'clock', | ||||||
| 	'activity', | 	'activity', | ||||||
| 	'photos', | 	'photos', | ||||||
| 	'digitalClock', | 	'digitalClock', | ||||||
| 	'federation', | 	'federation', | ||||||
| 	'instance-cloud', | 	'instanceCloud', | ||||||
| 	'postForm', | 	'postForm', | ||||||
| 	'slideshow', | 	'slideshow', | ||||||
| 	'serverMetric', | 	'serverMetric', | ||||||
|  |  | ||||||
							
								
								
									
										115
									
								
								packages/client/src/widgets/rss-marquee.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								packages/client/src/widgets/rss-marquee.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,115 @@ | ||||||
|  | <template> | ||||||
|  | <MkContainer :naked="widgetProps.transparent" :show-header="widgetProps.showHeader" class="mkw-rss-marquee"> | ||||||
|  | 	<template #header><i class="fas fa-rss-square"></i>RSS</template> | ||||||
|  | 	<template #func><button class="_button" @click="configure"><i class="fas fa-cog"></i></button></template> | ||||||
|  | 
 | ||||||
|  | 	<div class="ekmkgxbk"> | ||||||
|  | 		<MkLoading v-if="fetching"/> | ||||||
|  | 		<div v-else class="feed"> | ||||||
|  | 			<MarqueeText :duration="widgetProps.speed" :reverse="widgetProps.reverse"> | ||||||
|  | 				<a v-for="item in items" class="item" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a> | ||||||
|  | 			</MarqueeText> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | </MkContainer> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { onMounted, onUnmounted, ref, watch } from 'vue'; | ||||||
|  | import MarqueeText from 'vue-marquee-text-component'; | ||||||
|  | import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; | ||||||
|  | import { GetFormResultType } from '@/scripts/form'; | ||||||
|  | import * as os from '@/os'; | ||||||
|  | import MkContainer from '@/components/ui/container.vue'; | ||||||
|  | import { useInterval } from '@/scripts/use-interval'; | ||||||
|  | 
 | ||||||
|  | const name = 'rssMarquee'; | ||||||
|  | 
 | ||||||
|  | const widgetPropsDef = { | ||||||
|  | 	url: { | ||||||
|  | 		type: 'string' as const, | ||||||
|  | 		default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews', | ||||||
|  | 	}, | ||||||
|  | 	showHeader: { | ||||||
|  | 		type: 'boolean' as const, | ||||||
|  | 		default: false, | ||||||
|  | 	}, | ||||||
|  | 	transparent: { | ||||||
|  | 		type: 'boolean' as const, | ||||||
|  | 		default: false, | ||||||
|  | 	}, | ||||||
|  | 	speed: { | ||||||
|  | 		type: 'radio' as const, | ||||||
|  | 		default: 70, | ||||||
|  | 		options: [{ | ||||||
|  | 			value: 170, label: 'very slow', | ||||||
|  | 		}, { | ||||||
|  | 			value: 100, label: 'slow', | ||||||
|  | 		}, { | ||||||
|  | 			value: 70, label: 'medium', | ||||||
|  | 		}, { | ||||||
|  | 			value: 40, label: 'fast', | ||||||
|  | 		}, { | ||||||
|  | 			value: 20, label: 'very fast', | ||||||
|  | 		}], | ||||||
|  | 	}, | ||||||
|  | 	reverse: { | ||||||
|  | 		type: 'boolean' as const, | ||||||
|  | 		default: false, | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type WidgetProps = GetFormResultType<typeof widgetPropsDef>; | ||||||
|  | 
 | ||||||
|  | // 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない | ||||||
|  | //const props = defineProps<WidgetComponentProps<WidgetProps>>(); | ||||||
|  | //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); | ||||||
|  | const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); | ||||||
|  | const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); | ||||||
|  | 
 | ||||||
|  | const { widgetProps, configure } = useWidgetPropsManager(name, | ||||||
|  | 	widgetPropsDef, | ||||||
|  | 	props, | ||||||
|  | 	emit, | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | const items = ref([]); | ||||||
|  | const fetching = ref(true); | ||||||
|  | 
 | ||||||
|  | const tick = () => { | ||||||
|  | 	fetch(`https://api.rss2json.com/v1/api.json?rss_url=${widgetProps.url}`, {}).then(res => { | ||||||
|  | 		res.json().then(feed => { | ||||||
|  | 			items.value = feed.items; | ||||||
|  | 			fetching.value = false; | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | watch(() => widgetProps.url, tick); | ||||||
|  | 
 | ||||||
|  | useInterval(tick, 60000, { | ||||||
|  | 	immediate: true, | ||||||
|  | 	afterMounted: true, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | defineExpose<WidgetComponentExpose>({ | ||||||
|  | 	name, | ||||||
|  | 	configure, | ||||||
|  | 	id: props.widget ? props.widget.id : null, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .ekmkgxbk { | ||||||
|  | 	> .feed { | ||||||
|  | 		padding: 0; | ||||||
|  | 		font-size: 0.9em; | ||||||
|  | 
 | ||||||
|  | 		::v-deep(.item) { | ||||||
|  | 			display: inline-block; | ||||||
|  | 			color: var(--fg); | ||||||
|  | 			margin: 12px 3em 12px 0; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -6,7 +6,7 @@ | ||||||
| 	<div class="ekmkgxbj"> | 	<div class="ekmkgxbj"> | ||||||
| 		<MkLoading v-if="fetching"/> | 		<MkLoading v-if="fetching"/> | ||||||
| 		<div v-else class="feed"> | 		<div v-else class="feed"> | ||||||
| 			<a v-for="item in items" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a> | 			<a v-for="item in items" class="item" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| </MkContainer> | </MkContainer> | ||||||
|  | @ -23,14 +23,14 @@ import { useInterval } from '@/scripts/use-interval'; | ||||||
| const name = 'rss'; | const name = 'rss'; | ||||||
| 
 | 
 | ||||||
| const widgetPropsDef = { | const widgetPropsDef = { | ||||||
| 	showHeader: { |  | ||||||
| 		type: 'boolean' as const, |  | ||||||
| 		default: true, |  | ||||||
| 	}, |  | ||||||
| 	url: { | 	url: { | ||||||
| 		type: 'string' as const, | 		type: 'string' as const, | ||||||
| 		default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews', | 		default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews', | ||||||
| 	}, | 	}, | ||||||
|  | 	showHeader: { | ||||||
|  | 		type: 'boolean' as const, | ||||||
|  | 		default: true, | ||||||
|  | 	}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| type WidgetProps = GetFormResultType<typeof widgetPropsDef>; | type WidgetProps = GetFormResultType<typeof widgetPropsDef>; | ||||||
|  | @ -79,7 +79,7 @@ defineExpose<WidgetComponentExpose>({ | ||||||
| 		padding: 0; | 		padding: 0; | ||||||
| 		font-size: 0.9em; | 		font-size: 0.9em; | ||||||
| 
 | 
 | ||||||
| 		> a { | 		> .item { | ||||||
| 			display: block; | 			display: block; | ||||||
| 			padding: 8px 16px; | 			padding: 8px 16px; | ||||||
| 			color: var(--fg); | 			color: var(--fg); | ||||||
|  |  | ||||||
|  | @ -1221,6 +1221,11 @@ content-disposition@0.5.4: | ||||||
|   dependencies: |   dependencies: | ||||||
|     safe-buffer "5.2.1" |     safe-buffer "5.2.1" | ||||||
| 
 | 
 | ||||||
|  | core-js@^3.18.0: | ||||||
|  |   version "3.23.3" | ||||||
|  |   resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.23.3.tgz#3b977612b15da6da0c9cc4aec487e8d24f371112" | ||||||
|  |   integrity sha512-oAKwkj9xcWNBAvGbT//WiCdOMpb9XQG92/Fe3ABFM/R16BsHgePG00mFOgKf7IsCtfj8tA1kHtf/VwErhriz5Q== | ||||||
|  | 
 | ||||||
| core-util-is@1.0.2: | core-util-is@1.0.2: | ||||||
|   version "1.0.2" |   version "1.0.2" | ||||||
|   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" |   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" | ||||||
|  | @ -4252,12 +4257,20 @@ vue-eslint-parser@^9.0.1: | ||||||
|     lodash "^4.17.21" |     lodash "^4.17.21" | ||||||
|     semver "^7.3.6" |     semver "^7.3.6" | ||||||
| 
 | 
 | ||||||
|  | vue-marquee-text-component@2.0.1: | ||||||
|  |   version "2.0.1" | ||||||
|  |   resolved "https://registry.yarnpkg.com/vue-marquee-text-component/-/vue-marquee-text-component-2.0.1.tgz#62691df195f755471fa9bdc9b1969f836a922b9a" | ||||||
|  |   integrity sha512-dbeRwDY5neOJcWZrDFU2tJMhPSsxN25ZpNYeZdt0jkseg1MbyGKzrfEH9nrCFZRkEfqhxG+ukyzwVwR9US5sTQ== | ||||||
|  |   dependencies: | ||||||
|  |     core-js "^3.18.0" | ||||||
|  |     vue "^3.2.19" | ||||||
|  | 
 | ||||||
| vue-prism-editor@2.0.0-alpha.2: | vue-prism-editor@2.0.0-alpha.2: | ||||||
|   version "2.0.0-alpha.2" |   version "2.0.0-alpha.2" | ||||||
|   resolved "https://registry.yarnpkg.com/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz#aa53a88efaaed628027cbb282c2b1d37fc7c5c69" |   resolved "https://registry.yarnpkg.com/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz#aa53a88efaaed628027cbb282c2b1d37fc7c5c69" | ||||||
|   integrity sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w== |   integrity sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w== | ||||||
| 
 | 
 | ||||||
| vue@3.2.37: | vue@3.2.37, vue@^3.2.19: | ||||||
|   version "3.2.37" |   version "3.2.37" | ||||||
|   resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.37.tgz#da220ccb618d78579d25b06c7c21498ca4e5452e" |   resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.37.tgz#da220ccb618d78579d25b06c7c21498ca4e5452e" | ||||||
|   integrity sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ== |   integrity sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ== | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue