web4, se guardo indietro vedo il futuro
This commit is contained in:
commit
c69ba7e497
59 changed files with 12330 additions and 0 deletions
13
.editorconfig
Normal file
13
.editorconfig
Normal file
|
@ -0,0 +1,13 @@
|
|||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
90
.gitignore
vendored
Normal file
90
.gitignore
vendored
Normal file
|
@ -0,0 +1,90 @@
|
|||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
/logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# Nuxt generate
|
||||
dist
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
# IDE / Editor
|
||||
.idea
|
||||
|
||||
# Service worker
|
||||
sw.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Vim swap files
|
||||
*.swp
|
69
README.md
Normal file
69
README.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
# chew
|
||||
|
||||
## Build Setup
|
||||
|
||||
```bash
|
||||
# install dependencies
|
||||
$ yarn install
|
||||
|
||||
# serve with hot reload at localhost:3000
|
||||
$ yarn dev
|
||||
|
||||
# build for production and launch server
|
||||
$ yarn build
|
||||
$ yarn start
|
||||
|
||||
# generate static project
|
||||
$ yarn generate
|
||||
```
|
||||
|
||||
For detailed explanation on how things work, check out the [documentation](https://nuxtjs.org).
|
||||
|
||||
## Special Directories
|
||||
|
||||
You can create the following extra directories, some of which have special behaviors. Only `pages` is required; you can delete them if you don't want to use their functionality.
|
||||
|
||||
### `assets`
|
||||
|
||||
The assets directory contains your uncompiled assets such as Stylus or Sass files, images, or fonts.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/assets).
|
||||
|
||||
### `components`
|
||||
|
||||
The components directory contains your Vue.js components. Components make up the different parts of your page and can be reused and imported into your pages, layouts and even other components.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/components).
|
||||
|
||||
### `layouts`
|
||||
|
||||
Layouts are a great help when you want to change the look and feel of your Nuxt app, whether you want to include a sidebar or have distinct layouts for mobile and desktop.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/layouts).
|
||||
|
||||
|
||||
### `pages`
|
||||
|
||||
This directory contains your application views and routes. Nuxt will read all the `*.vue` files inside this directory and setup Vue Router automatically.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/get-started/routing).
|
||||
|
||||
### `plugins`
|
||||
|
||||
The plugins directory contains JavaScript plugins that you want to run before instantiating the root Vue.js Application. This is the place to add Vue plugins and to inject functions or constants. Every time you need to use `Vue.use()`, you should create a file in `plugins/` and add its path to plugins in `nuxt.config.js`.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/plugins).
|
||||
|
||||
### `static`
|
||||
|
||||
This directory contains your static files. Each file inside this directory is mapped to `/`.
|
||||
|
||||
Example: `/static/robots.txt` is mapped as `/robots.txt`.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/static).
|
||||
|
||||
### `store`
|
||||
|
||||
This directory contains your Vuex store files. Creating a file in this directory automatically activates Vuex.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/store).
|
4
assets/variables.scss
Normal file
4
assets/variables.scss
Normal file
|
@ -0,0 +1,4 @@
|
|||
// Ref: https://github.com/nuxt-community/vuetify-module#customvariables
|
||||
//
|
||||
// The variables you want to modify
|
||||
// $font-size-root: 20px;
|
58
components/AddCohort.vue
Normal file
58
components/AddCohort.vue
Normal file
|
@ -0,0 +1,58 @@
|
|||
<template>
|
||||
<article class='card inline-block'>
|
||||
<input type="text" v-model='name' placeholder="Name">
|
||||
<input type="text" v-model='description' placeholder="Description">
|
||||
|
||||
<ul>
|
||||
<li class="p-2 bg-slate-700 " v-for='source, id in sources' :key="id">
|
||||
<div class="font-bold text-xl">{{ source.name }}</div>
|
||||
<span class="text-red-500">{{source.description}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<button @click='addCohort' class="p-2 text-xl font-bold border-2 rounded mt-5">Create +</button>
|
||||
</article>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
modelValue: String
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
name: '',
|
||||
sources: '',
|
||||
url: '',
|
||||
error: '' }
|
||||
},
|
||||
methods: {
|
||||
async addCohort () {
|
||||
const cohort = await $fetch('/api/cohort', { method: 'POST', body: {
|
||||
name: this.name,
|
||||
sources: this.sources.map(s => s.id)
|
||||
}})
|
||||
this.$emit("addCohort", cohort)
|
||||
},
|
||||
async addSource() {
|
||||
console.error('dentro add source', this.url, this.name)
|
||||
this.error = ''
|
||||
// invio un url al backend
|
||||
// se e' un feed valido, lo aggiungo ai sources e all cohort appena creata
|
||||
// se non e' valido provo a cercare il feed dentro quell'url
|
||||
try {
|
||||
const source = await $fetch('/api/source', { method: 'POST', body: { URL: this.url } })
|
||||
this.sources.push( source )
|
||||
// this.$emit('addCohort', { id: 1, title: this.title })
|
||||
this.url = ''
|
||||
} catch (e) {
|
||||
this.error = String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
@apply w-full rounded-md border-2 border-dashed p-2 mt-3
|
||||
max-w-sm border-pink-500;
|
||||
}
|
||||
</style>
|
60
components/AddSource.vue
Normal file
60
components/AddSource.vue
Normal file
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<article class='card inline-block'>
|
||||
<h3 class='text-xl font-bold'>Add source</h3>
|
||||
<span>Aggiungi una sorgente, un sito, un feed rss/atom/json, un account mastodon, twitter, facebook</span>
|
||||
<p>{{search}} {{items.length}}</p>
|
||||
<input type="text" v-model='url' placeholder="Feed URL" @keydown.enter="addSource">
|
||||
<span v-if='error' class="text-red-500">{{error}}</span>
|
||||
<span v-else class="font-light">Add your feed</span>
|
||||
<!-- <ul>
|
||||
<li class="p-2 bg-slate-700 " v-for='source, id in sources' :key="id">
|
||||
<div class="font-bold text-xl">{{ source.name }}</div>
|
||||
<span class="text-red-500">{{source.description}}</span>
|
||||
</li>
|
||||
</ul> -->
|
||||
<button @click='addCohort' class="p-2 text-xl font-bold border-2 rounded mt-5">Create +</button>
|
||||
</article>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
// emits: ['update:modelValue', 'addSource'],
|
||||
// async function runSearch() {
|
||||
// console.error('sono dentro run search', search.value)
|
||||
// items.value = await $fetch('/api/source/search', { params: { search: search.value }})
|
||||
// console.error(items)
|
||||
// }
|
||||
|
||||
// let items = reactive([])
|
||||
// return { source, url, error, search, items }
|
||||
// },
|
||||
// asyncComputed: {
|
||||
// async items () {
|
||||
// console.error('dentro items vado di search', this.search)
|
||||
// return await $fetch('/api/source/search', { search: this.search })
|
||||
// }
|
||||
// },
|
||||
methods: {
|
||||
async addSource() {
|
||||
console.error('dentro add source', this.url, this.name)
|
||||
this.error = ''
|
||||
// invio un url al backend
|
||||
// se e' un feed valido, lo aggiungo ai sources e all cohort appena creata
|
||||
// se non e' valido provo a cercare il feed dentro quell'url
|
||||
try {
|
||||
const source = await $fetch('/api/source', { method: 'POST', body: { URL: this.url } })
|
||||
this.source.push( source )
|
||||
// this.$emit('addCohort', { id: 1, title: this.title })
|
||||
this.url = ''
|
||||
} catch (e) {
|
||||
this.error = String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
@apply w-full rounded-md border-2 border-dashed p-2 mt-3
|
||||
max-w-sm border-pink-500;
|
||||
}
|
||||
</style>
|
59
components/Cohort.vue
Normal file
59
components/Cohort.vue
Normal file
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<v-card>
|
||||
<v-card-title>{{cohort.name}}</v-card-title>
|
||||
<v-card-subtitle>{{cohort.description}}</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<span>{{cohort}} {{posts}}</span>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<!-- <h3 class="font-bold text-2xl text-gray-600 bg-gray-100 p-2">{{cohort.name}}</h3>
|
||||
<span class="float-right text-xs">{{cohort.dailyView}} <RssIcon class='h-8'/></span>
|
||||
<ul v-if='withContent'>
|
||||
<a v-for='post in posts' :key="post.URL" target="_blank" :href='post.URL' class="hover:bg-zinc-200 block pt-2 px-2">
|
||||
<div class="font-bold text-black" v-html='post.title'></div>
|
||||
<span class="font-base text-xs">
|
||||
{{new Date(post.date).toLocaleString()}}
|
||||
<b>{{post.source.name}}</b>
|
||||
</span>
|
||||
<span v-html='post.summary'></span>
|
||||
</a>
|
||||
</ul>
|
||||
</div> -->
|
||||
</template>
|
||||
<script>
|
||||
// import { RssIcon } from '@heroicons/vue/solid'
|
||||
export default {
|
||||
// components: { RssIcon },
|
||||
data: () => ({
|
||||
posts: []
|
||||
}),
|
||||
props: {
|
||||
cohort: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
withContent: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxPosts: {
|
||||
type: Number,
|
||||
default: 10
|
||||
}
|
||||
},
|
||||
async fetch() {
|
||||
console.error('dentro fetch')
|
||||
console.error(this.cohort)
|
||||
this.posts = await this.$http.$get(`http://localhost:3000/api/cohort/${this.cohort.id}`,
|
||||
{ params: { maxPosts: this.maxPosts }})
|
||||
// console.error(res)
|
||||
// .then(res => res.json())
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
@apply w-full rounded-md mt-3 inline-block mr-1
|
||||
border-pink-500;
|
||||
}
|
||||
</style>
|
50
components/Post.vue
Normal file
50
components/Post.vue
Normal file
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<a class='post' :href='post.URL' target='_blank'>
|
||||
<v-row class='mb-5'>
|
||||
<v-col cols=12 lg=4>
|
||||
<v-img class='rounded-lg' max-height="250" :src="post.image" alt=""></v-img>
|
||||
</v-col>
|
||||
<v-col cols=12 lg=8>
|
||||
<span>{{post.source.name}}</span>
|
||||
<h1 class='text-h5 font-weight-bold'>{{post.title}}</h1>
|
||||
<p class='font-weight-light'>{{new Date(post.date).toLocaleString('it-IT', { weekday: 'long', month: 'long', day: 'numeric'})}}</p>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</a>
|
||||
<!-- <div class='post p-2 mb-10 bg-white'>
|
||||
<div role='title' class="block pt-2 mb-3 border-b-2" >
|
||||
<a target="_blank" :href='post.URL' v-html='post.title' class='text-xl font-bold text-violet-500'></a>
|
||||
<span class="font-base text-base float-right">{{new Date(post.date).toLocaleString()}} - <b><a :href='post.source.link'>{{post.source.name}} <img class="inline h-5 w-5 rounded-full" :src="post.source.image || post.source.link + 'favicon.ico'" alt="" /></a></b></span>
|
||||
</div>
|
||||
<span class='text-xl' v-html='post.summary || post.content'></span>
|
||||
</div> -->
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
post: Object
|
||||
},
|
||||
// async setup(props) {
|
||||
// const res = await fetch(`http://localhost:3000/api/cohort/${props.cohort.id}`).then(res => res.json())
|
||||
// return { cohort: res.cohort, posts: res.posts }
|
||||
// },
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.post img {
|
||||
max-width: 500px;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
a.post{
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.post:hover {
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
37
components/TextCohort.vue
Normal file
37
components/TextCohort.vue
Normal file
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div v-if='posts'>
|
||||
<h3 class="font-extrabold text-3xl text-pink-500 p-2 mt-10 rounded border-2 border-pink-500">{{cohort.name}}</h3>
|
||||
<div v-for='post in posts' :key="post.URL" class='p-2 mb-10 bg-white'>
|
||||
<div role='title' class="block pt-2 mb-3 border-b-2" >
|
||||
<a target="_blank" :href='post.URL' v-html='post.title' class='text-xl font-bold text-violet-500'></a>
|
||||
<span class="font-base text-base float-right">{{new Date(post.date).toLocaleString()}} - <b><a :href='post.source.link'>{{post.source.name}} <img class="inline h-5 w-5 rounded-full" :src="post.source.image || post.source.link + 'favicon.ico'" alt="" /></a></b></span>
|
||||
</div>
|
||||
<span class='text-xl' v-html='post.summary'></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
cohort: Object
|
||||
},
|
||||
async fetch(props) {
|
||||
const res = await fetch(`http://localhost:3000/api/cohort/${props.cohort.id}`).then(res => res.json())
|
||||
return { cohort: res.cohort, posts: res.posts }
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
@apply w-full rounded-md border-2 bg-stone-800 mt-3 inline-block
|
||||
max-w-sm border-pink-500;
|
||||
|
||||
.title {
|
||||
@apply text-lime-300 p-0 m-2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
60
components/components/Cohort.vue
Normal file
60
components/components/Cohort.vue
Normal file
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<div class="rounded overflow-hidden shadow-lg">
|
||||
<div class="px-6 py-4">
|
||||
<div class="font-bold text-xl mb-2">{{cohort.name}}</div>
|
||||
<p class="text-gray-700 text-base">{{cohort.description}}</p>
|
||||
</div>
|
||||
<ul class="px-6 py-4 bg-grey-300 text-pink-600 p-1">
|
||||
<li class="font-monospace">Last update</li>
|
||||
<li class="font-monospace">Views</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- <h3 class="font-bold text-2xl text-gray-600 bg-gray-100 p-2">{{cohort.name}}</h3>
|
||||
<span class="float-right text-xs">{{cohort.dailyView}} <RssIcon class='h-8'/></span>
|
||||
<ul v-if='withContent'>
|
||||
<a v-for='post in posts' :key="post.URL" target="_blank" :href='post.URL' class="hover:bg-zinc-200 block pt-2 px-2">
|
||||
<div class="font-bold text-black" v-html='post.title'></div>
|
||||
<span class="font-base text-xs">
|
||||
{{new Date(post.date).toLocaleString()}}
|
||||
<b>{{post.source.name}}</b>
|
||||
</span>
|
||||
<span v-html='post.summary'></span>
|
||||
</a>
|
||||
</ul>
|
||||
</div> -->
|
||||
</template>
|
||||
<script>
|
||||
// import { RssIcon } from '@heroicons/vue/solid'
|
||||
export default ({
|
||||
// components: { RssIcon },
|
||||
props: {
|
||||
cohort: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
withContent: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxPosts: {
|
||||
type: Number,
|
||||
default: 10
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
// cohort:
|
||||
}),
|
||||
async fetch () {
|
||||
const res = await $fetch(`http://localhost:3000/api/cohort/${props.cohort.id}`,
|
||||
{ params: { maxPosts: props.maxPosts }})
|
||||
// .then(res => res.json())
|
||||
return res
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
@apply w-full rounded-md mt-3 inline-block mr-1
|
||||
border-pink-500;
|
||||
}
|
||||
</style>
|
21
layouts/default.vue
Normal file
21
layouts/default.vue
Normal file
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<v-app>
|
||||
<v-main light>
|
||||
<v-tabs centered>
|
||||
<v-tab to='/'>Home</v-tab>
|
||||
<v-tab to='/groups'>Groups</v-tab>
|
||||
<v-tab to='/add'>Add</v-tab>
|
||||
<v-tab to='/embed'>Widget</v-tab>
|
||||
</v-tabs>
|
||||
<v-container>
|
||||
<Nuxt />
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DefaultLayout'
|
||||
}
|
||||
</script>
|
45
layouts/error.vue
Normal file
45
layouts/error.vue
Normal file
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<v-app dark>
|
||||
<h1 v-if="error.statusCode === 404">
|
||||
{{ pageNotFound }}
|
||||
</h1>
|
||||
<h1 v-else>
|
||||
{{ otherError }}
|
||||
</h1>
|
||||
<NuxtLink to="/">
|
||||
Home page
|
||||
</NuxtLink>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'EmptyLayout',
|
||||
layout: 'empty',
|
||||
props: {
|
||||
error: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
pageNotFound: '404 Not Found',
|
||||
otherError: 'An error occurred'
|
||||
}
|
||||
},
|
||||
head () {
|
||||
const title =
|
||||
this.error.statusCode === 404 ? this.pageNotFound : this.otherError
|
||||
return {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
</style>
|
60
lit/display-feed.js
Normal file
60
lit/display-feed.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { LitElement, html } from 'lit'
|
||||
import { until } from 'lit/directives/until.js'
|
||||
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'
|
||||
|
||||
export class DisplayFeed extends LitElement {
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
max: { type: Boolean },
|
||||
posts: { type: Array, state: true },
|
||||
feed: { type: String },
|
||||
title: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.title = 'Display Feed'
|
||||
}
|
||||
|
||||
_post (post) {
|
||||
return html`<div class="df-item"><h3 class="df-title">${post.title}</h3><div class="df-content">${unsafeHTML(post.content)}</div></div>`
|
||||
}
|
||||
|
||||
// connectedCallback () {
|
||||
// super.connectedCallback()
|
||||
// console.error
|
||||
// this.posts = fetch(this.feed)
|
||||
// .then(async res => {
|
||||
// const posts = await res.json()
|
||||
// return posts.posts.map(this._post)
|
||||
// })
|
||||
// }
|
||||
|
||||
updated (changedProperties) {
|
||||
console.error('dentro changed ', changedProperties)
|
||||
console.error(this.feed)
|
||||
console.error(changedProperties)
|
||||
if (changedProperties.has('feed')) {
|
||||
console.error('feed cambiato')
|
||||
this.posts = fetch(this.feed)
|
||||
.then(async res => {
|
||||
const posts = await res.json()
|
||||
return posts.posts.map(this._post)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// const loading = html`<span>Loading...</span>`
|
||||
// const title = html`<h2>${this.title}</h2>`
|
||||
return html`<h2>${this.title}</h2>${until(this.posts, 'Loading...')}`
|
||||
}
|
||||
|
||||
// do not create a shadowDOM (we want style pollution from outside)
|
||||
createRenderRoot() { return this }
|
||||
}
|
||||
|
||||
|
||||
customElements.define('display-feed', DisplayFeed)
|
13
lit/index.html
Normal file
13
lit/index.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<script type="module" src="./dist/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<display-feed feed='http://localhost:3000/api/source/33'></display-feed>
|
||||
</body>
|
||||
</html>
|
1
lit/node_modules
Symbolic link
1
lit/node_modules
Symbolic link
|
@ -0,0 +1 @@
|
|||
../node_modules/
|
95
nuxt.config.js
Normal file
95
nuxt.config.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
import colors from 'vuetify/es5/util/colors'
|
||||
|
||||
export default {
|
||||
// Global page headers: https://go.nuxtjs.dev/config-head
|
||||
head: {
|
||||
titleTemplate: '%s - chew',
|
||||
title: 'chew',
|
||||
htmlAttrs: {
|
||||
lang: 'en'
|
||||
},
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ hid: 'description', name: 'description', content: '' },
|
||||
{ name: 'format-detection', content: 'telephone=no' }
|
||||
],
|
||||
script: [{ src: '/display-feed.js' }],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
|
||||
]
|
||||
},
|
||||
|
||||
// Global CSS: https://go.nuxtjs.dev/config-css
|
||||
css: [
|
||||
],
|
||||
|
||||
render: {
|
||||
static: {
|
||||
// Add CORS header to static files.
|
||||
setHeaders(res) {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET');
|
||||
res.setHeader(
|
||||
'Access-Control-Allow-Headers',
|
||||
'Origin, X-Requested-With, Content-Type, Accept'
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
|
||||
plugins: [
|
||||
],
|
||||
|
||||
// Auto import components: https://go.nuxtjs.dev/config-components
|
||||
components: true,
|
||||
|
||||
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
|
||||
buildModules: [
|
||||
// https://go.nuxtjs.dev/vuetify
|
||||
'@nuxtjs/vuetify',
|
||||
],
|
||||
|
||||
// Modules: https://go.nuxtjs.dev/config-modules
|
||||
modules: [
|
||||
'@nuxt/http',
|
||||
'@/server/initialize.server.js'
|
||||
],
|
||||
|
||||
serverMiddleware: ['server/index'],
|
||||
|
||||
vue: {
|
||||
config: {
|
||||
ignoredElements: ['display-feed']
|
||||
}
|
||||
},
|
||||
|
||||
// Vuetify module configuration: https://go.nuxtjs.dev/config-vuetify
|
||||
vuetify: {
|
||||
customVariables: ['~/assets/variables.scss'],
|
||||
theme: {
|
||||
dark: false,
|
||||
themes: {
|
||||
dark: {
|
||||
primary: colors.blue.darken2,
|
||||
accent: colors.grey.darken3,
|
||||
secondary: colors.amber.darken3,
|
||||
info: colors.teal.lighten1,
|
||||
warning: colors.amber.base,
|
||||
error: colors.deepOrange.accent4,
|
||||
success: colors.green.accent3
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Build Configuration: https://go.nuxtjs.dev/config-build
|
||||
build: {
|
||||
build: {
|
||||
corejs: 3,
|
||||
cache: true,
|
||||
hardSource: true
|
||||
}
|
||||
}
|
||||
}
|
37
package.json
Normal file
37
package.json
Normal file
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "chew",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "nuxt",
|
||||
"build": "nuxt build",
|
||||
"start": "nuxt start",
|
||||
"generate": "nuxt generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/http": "^0.6.4",
|
||||
"@prisma/client": "^3.8.1",
|
||||
"body-parser": "^1.19.1",
|
||||
"bull": "^4.2.1",
|
||||
"core-js": "^3.20.3",
|
||||
"cors": "^2.8.5",
|
||||
"dompurify": "^2.3.4",
|
||||
"express": "^4.17.2",
|
||||
"feed": "^4.2.2",
|
||||
"feedparser": "^2.2.10",
|
||||
"jsdom": "^19.0.0",
|
||||
"linkedom": "^0.13.0",
|
||||
"lit": "^2.1.1",
|
||||
"nuxt": "^2.15.8",
|
||||
"vue": "^2.6.14",
|
||||
"vue-server-renderer": "^2.6.14",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"vuetify": "^2.6.1",
|
||||
"webpack": "^4.46.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/vuetify": "^1.12.3",
|
||||
"prisma": "^3.8.1",
|
||||
"webpack-cli": "^4.9.1"
|
||||
}
|
||||
}
|
93
pages/add.vue
Normal file
93
pages/add.vue
Normal file
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<section>
|
||||
<v-data-table dense
|
||||
:items="sources"
|
||||
:headers="headers">
|
||||
<template v-slot:item.updatedAt="{ item }">
|
||||
<span>{{new Date(item.updatedAt).toLocaleString()}}</span>
|
||||
</template>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-btn target="_blank" :href='`/api/source/${item.id}.rss`' small text color='orange' label='rss feed'><v-icon>mdi-rss</v-icon> Rss</v-btn>
|
||||
<v-btn @click='copy($event, item)' small text color='primary' label='embedd'><v-icon>mdi-application-brackets-outline</v-icon> Embed</v-btn>
|
||||
</template>
|
||||
</v-data-table>
|
||||
<p class='text-xl'>Add a website or a feed rss/atom/jsonfeed</p>
|
||||
<v-text-field v-model='url' outlined label='Add URL' required></v-text-field>
|
||||
<v-btn @click='addSource' :loading='loading' :error-messages='error' :disabled='loading'>Add</v-btn>
|
||||
</section>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data: () => ({
|
||||
url: '',
|
||||
error: '',
|
||||
loading: false,
|
||||
sources: [],
|
||||
headers: [
|
||||
{
|
||||
text: 'Actions',
|
||||
value: 'actions'
|
||||
},
|
||||
{
|
||||
text: 'Name',
|
||||
value: 'name',
|
||||
},
|
||||
{
|
||||
text: 'Description',
|
||||
value: 'description'
|
||||
},
|
||||
{
|
||||
text: 'Updated',
|
||||
value: 'updatedAt'
|
||||
},
|
||||
]
|
||||
}),
|
||||
async fetch () {
|
||||
this.sources = await this.$http.$get('/api/source')
|
||||
},
|
||||
methods: {
|
||||
copy (ev, item) {
|
||||
console.error('dentro copy')
|
||||
const str = `<display-feed feed='http://localhost:3000/api/source/${item.id}'></display-feed>`
|
||||
try {
|
||||
navigator.clipboard.writeText(str)
|
||||
} catch (e) {
|
||||
const el = document.createElement('textarea')
|
||||
el.addEventListener('focusin', e => e.stopPropagation())
|
||||
el.value = str
|
||||
document.body.appendChild(el)
|
||||
el.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(el)
|
||||
}
|
||||
},
|
||||
async addSource() {
|
||||
this.loading = true
|
||||
this.error = false
|
||||
// add http if not specified
|
||||
this.url = this.url.match(/^https?:\/\//) ? this.url : 'http://' + this.url
|
||||
console.error(this.url)
|
||||
this.error = ''
|
||||
// invio un url al backend
|
||||
// se e' un feed valido, lo aggiungo ai sources e all cohort appena creata
|
||||
// se non e' valido provo a cercare il feed dentro quell'url
|
||||
try {
|
||||
const source = await this.$http.$post('/api/source', { URL: this.url })
|
||||
this.sources.unshift( source )
|
||||
// this.$emit('addCohort', { id: 1, title: this.title })
|
||||
// this.url = ''
|
||||
} catch (e) {
|
||||
this.error = String(e)
|
||||
console.error(this.error)
|
||||
}
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
@apply w-full rounded-md border-2 border-dashed p-2 mt-3
|
||||
max-w-sm border-pink-500;
|
||||
}
|
||||
</style>
|
70
pages/embed.vue
Normal file
70
pages/embed.vue
Normal file
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<section>
|
||||
<v-card>
|
||||
<v-card-title>Embed to your website</v-card-title>
|
||||
<v-card-text>
|
||||
<v-autocomplete
|
||||
v-model='source'
|
||||
:search-input.sync="search"
|
||||
label='Source'
|
||||
hide-no-data
|
||||
item-value="id"
|
||||
item-text="name"
|
||||
:items="sources"
|
||||
:disabled='loading'
|
||||
:loading='loading'
|
||||
prepend-icon="mdi-magnify"
|
||||
placeholder="Start typing to search for a source to add"
|
||||
clearable
|
||||
return-object
|
||||
no-filter>
|
||||
<template v-slot:item="{ item }">
|
||||
<v-list-item-content three-line>
|
||||
<v-list-item-title>{{item.name}}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ item.description }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>Last update: {{ item.updatedAt }} / Tags: / Link: {{item.link}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
<v-alert class='white--text blue-grey darken-2' v-text='code'></v-alert>
|
||||
<display-feed :feed="feed"></display-feed>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</section>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data: () => ({
|
||||
source: {},
|
||||
loading: false,
|
||||
search: '',
|
||||
sources: [],
|
||||
cohorts: []
|
||||
}),
|
||||
watch: {
|
||||
async search (value) {
|
||||
if (!value) return
|
||||
this.makeSearch()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
code () {
|
||||
return `<display-feed feed="http://localhost:3000/api/source/${this.source && this.source.id}"></display-feed>`
|
||||
},
|
||||
feed () {
|
||||
if (this.source && this.source.id) {
|
||||
return `http://localhost:3000/api/source/${this.source.id}`
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async makeSearch () {
|
||||
this.loading = true
|
||||
this.sources = await this.$http.$get(`/api/source/search?search=${encodeURIComponent(this.search)}`)
|
||||
this.loading = false
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
54
pages/group/_id.vue
Normal file
54
pages/group/_id.vue
Normal file
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<section class='mt-4'>
|
||||
<!-- <h3 class='text-3xl font-extrabold'>{{cohort.name}}</h3> -->
|
||||
<div class="font-bold text-3xl">{{cohort.name}}</div>
|
||||
<div class='font-light mb-2'>{{cohort.dailyView}} views/day, last update: {{posts[0].updatedAt}}</div>
|
||||
<button class='p-2 rounded bg-orange-100 hover:bg-orange-200 mr-1 shadow'>Feed <RssIcon class='fill-orange-600 inline h-5'/></button>
|
||||
<button class='p-2 rounded bg-orange-100 hover:bg-orange-200 mr-1 shadow'>Embedd <ShareIcon class='inline h-5'/></button>
|
||||
<p class="text-gray-700 text-base">{{cohort.description}}</p>
|
||||
<ul>
|
||||
<Post v-for='post in posts' :key='post.id' :post='post'/>
|
||||
<!-- <a v-for='post in posts' :key="post.URL" target="_blank" :href='post.URL' class="hover:bg-zinc-200 block pt-2 px-2">
|
||||
<div class="font-bold text-black" v-html='post.title'></div>
|
||||
<span class="font-base text-xs">
|
||||
{{new Date(post.date).toLocaleString()}}
|
||||
<b>{{post.source.name}}</b>
|
||||
</span>
|
||||
<span v-html='post.summary'></span>
|
||||
</a> -->
|
||||
</ul>
|
||||
</section>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
// import { RssIcon, ShareIcon } from '@heroicons/vue/solid'
|
||||
|
||||
// const route = useRoute()
|
||||
// const cohort = await $fetch('/api/cohort/' + route.params.id)
|
||||
// const { cohort, posts } = await $fetch('http://localhost:3000/api/cohort/' + route.params.id)
|
||||
// console.error(cohort)
|
||||
// import Cohort from '@/components/Cohort.vue'
|
||||
// export default {
|
||||
// components: { Cohort },
|
||||
// async setup () {
|
||||
|
||||
// }
|
||||
// }
|
||||
// data: () => ({ cohorts: [] }),
|
||||
// async setup() {
|
||||
// // const lastPosts = await fetch('http://localhost:3000/api/posts').then(res => res.json())
|
||||
// const cohorts = await fetch('http://localhost:3000/api/cohorts').then(res => res.json())
|
||||
// console.error(cohorts)
|
||||
|
||||
// return { cohorts }
|
||||
// },
|
||||
// methods: {
|
||||
// addCohort (cohort) {
|
||||
// console.error('dentro add cohort', cohort)
|
||||
// this.cohorts.push(cohort)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
</script>
|
136
pages/groups.vue
Normal file
136
pages/groups.vue
Normal file
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<section class='mt-4'>
|
||||
<v-data-table dense
|
||||
:items="cohorts"
|
||||
:headers="headers">
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-btn target="_blank" :href='`/api/cohort/${item.id}.rss`' small text color='orange' label='rss feed'><v-icon>mdi-rss</v-icon> Rss</v-btn>
|
||||
<v-btn @click='copy($event, item)' small text color='primary' label='embedd'><v-icon>mdi-application-brackets-outline</v-icon> Embed</v-btn>
|
||||
</template>
|
||||
</v-data-table>
|
||||
<v-card>
|
||||
<v-card-title>Add your group</v-card-title>
|
||||
<v-card-subtitle>Search for a source to add</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-text-field label='Name' v-model='cohort.name'></v-text-field>
|
||||
<v-text-field label='Description' v-model='cohort.description'></v-text-field>
|
||||
<v-autocomplete
|
||||
v-model='source'
|
||||
:search-input.sync="search"
|
||||
label='Source'
|
||||
hide-no-data
|
||||
item-value="id"
|
||||
item-text="name"
|
||||
:items="sources"
|
||||
:disabled='loading'
|
||||
:loading='loading'
|
||||
prepend-icon="mdi-magnify"
|
||||
placeholder="Start typing to search for a source to add"
|
||||
clearable
|
||||
return-object
|
||||
no-filter>
|
||||
<template v-slot:append-outer>
|
||||
<v-btn text link :disabled='!source' @click='addSource'>Add this source</v-btn>
|
||||
</template>
|
||||
<template v-slot:item="{ item }">
|
||||
<v-list-item-content three-line>
|
||||
<v-list-item-title>{{item.name}}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ item.description }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>Last update: {{ item.updatedAt }} / Tags: / Link: {{item.link}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
<v-btn @click='addCohort' text color='primary'>Create group</v-btn>
|
||||
<v-list>
|
||||
<v-list-item v-for='source in cohortSources' :key='source.id'>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{source.name}} <small>{{source.link}}</small></v-list-item-title>
|
||||
<v-list-item-subtitle>{{source.description}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<!-- <section class="flex place-content-center gap-5">
|
||||
<add-cohort></add-cohort>
|
||||
</section> -->
|
||||
<!-- <v-row>
|
||||
<v-col>
|
||||
<nuxt-link :to='`/group/${cohort.id}`' v-for='cohort in cohorts' :key='cohort.id'>
|
||||
<cohort :cohort='cohort' :max-posts='5'/>
|
||||
</nuxt-link>
|
||||
</v-col>
|
||||
</v-row> -->
|
||||
|
||||
</section>
|
||||
</template>
|
||||
<script>
|
||||
// import AddCohort from '@/components/AddCohort.vue'
|
||||
// import debounce from 'lodash/debounce'
|
||||
import Cohort from '@/components/Cohort.vue'
|
||||
// import Post from '@/components/Post.vue'
|
||||
// import TextCohort from '@/components/TextCohort.vue'
|
||||
|
||||
export default {
|
||||
components: { Cohort },
|
||||
data: () => ({
|
||||
cohort: {},
|
||||
loading: true,
|
||||
source: null,
|
||||
search: '',
|
||||
cohorts: [],
|
||||
cohortSources: [],
|
||||
sources: [],
|
||||
loading: false,
|
||||
headers: [
|
||||
{ text: 'Actions', value: 'actions' },
|
||||
{ text: 'Name', value: 'name' },
|
||||
{ text: 'Description', value: 'description' },
|
||||
{ text: 'Updated', value: 'updatedAt' },
|
||||
{ text: 'Sources', value: 'sources' },
|
||||
]
|
||||
}),
|
||||
async fetch() {
|
||||
// const lastPosts = await fetch('http://localhost:3000/api/posts').then(res => res.json())
|
||||
this.cohorts = await this.$http.$get('http://localhost:3000/api/cohorts')
|
||||
},
|
||||
watch: {
|
||||
async search (value) {
|
||||
if (!value) return
|
||||
this.makeSearch()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async makeSearch () {
|
||||
this.loading = true
|
||||
this.sources = await this.$http.$get(`/api/source/search?search=${encodeURIComponent(this.search)}`)
|
||||
this.loading = false
|
||||
},
|
||||
async addCohort () {
|
||||
const cohort = await this.$http.$post('/api/cohort', { ...this.cohort, sources: this.cohortSources.map(s => s.id) })
|
||||
this.cohorts.unshift(cohort)
|
||||
this.cohort = {}
|
||||
},
|
||||
async addSource () {
|
||||
this.cohortSources.unshift(this.source)
|
||||
this.source = null
|
||||
this.sources = []
|
||||
},
|
||||
copy (ev, item) {
|
||||
console.error('dentro copy')
|
||||
const str = `<display-feed feed='http://localhost:3000/api/cohort/${item.id}'></display-feed>`
|
||||
try {
|
||||
navigator.clipboard.writeText(str)
|
||||
} catch (e) {
|
||||
const el = document.createElement('textarea')
|
||||
el.addEventListener('focusin', e => e.stopPropagation())
|
||||
el.value = str
|
||||
document.body.appendChild(el)
|
||||
el.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(el)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
41
pages/index.vue
Normal file
41
pages/index.vue
Normal file
|
@ -0,0 +1,41 @@
|
|||
<template>
|
||||
<section>
|
||||
<h3 class="font-weight-bold text-h4 p-2 mt-10 rounded border-2 border-pink-500">Latest posts</h3>
|
||||
<v-container class='px-6 mx-6 max-w-80'>
|
||||
<Post v-for='post in lastPosts' :key='post.URL' :post='post'/>
|
||||
</v-container>
|
||||
<!-- //- TextCohort.mt-1(v-for='cohort in cohorts' :key='cohort.id' :cohort='cohort')
|
||||
//- li(v-for='post, id in lastPosts' :key='id')
|
||||
//- .title {{post.title}}
|
||||
//- <div>
|
||||
//- <p></p>
|
||||
//- <ul v-for='post, id in posts' :key='id'>
|
||||
//- <li>
|
||||
//- <span>{{post.title}} {{post.date}} {{post.url}}</span>
|
||||
//- <p v-html='post.content' />
|
||||
//- </li>
|
||||
//- </ul>
|
||||
//- </div> -->
|
||||
</section>
|
||||
</template>
|
||||
<script>
|
||||
// import AddCohort from '@/components/AddCohort.vue'
|
||||
// import Cohort from '@/components/Cohort.vue'
|
||||
// import TextCohort from '@/components/TextCohort.vue'
|
||||
|
||||
export default {
|
||||
// components: { AddCohort, Cohort, TextCohort, Post },
|
||||
data: () => ({ lastPosts: [], cohorts: [] }),
|
||||
async fetch() {
|
||||
this.lastPosts = await this.$http.$get('http://localhost:3000/api/posts')
|
||||
// this.cohorts = await this.$http.$get('http://localhost:3000/api/cohorts')
|
||||
// const cohorts = []
|
||||
},
|
||||
methods: {
|
||||
addCohort (cohort) {
|
||||
console.error('dentro add cohort', cohort)
|
||||
this.cohorts.push(cohort)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
82
prisma/migrations/20211214120439_init/migration.sql
Normal file
82
prisma/migrations/20211214120439_init/migration.sql
Normal file
|
@ -0,0 +1,82 @@
|
|||
-- CreateTable
|
||||
CREATE TABLE `Cohort` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Post` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`date` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updatedAt` DATETIME(3) NOT NULL,
|
||||
`title` VARCHAR(191) NOT NULL,
|
||||
`content` TEXT NOT NULL,
|
||||
`summary` VARCHAR(191) NOT NULL DEFAULT '',
|
||||
`sourceId` INTEGER NOT NULL,
|
||||
`URL` VARCHAR(191) NOT NULL,
|
||||
|
||||
UNIQUE INDEX `Post_URL_key`(`URL`),
|
||||
INDEX `Post_sourceId_fkey`(`sourceId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Source` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
`type` ENUM('FEED') NOT NULL DEFAULT 'FEED',
|
||||
`URL` VARCHAR(191) NOT NULL,
|
||||
`updatedAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`lastError` VARCHAR(191) NULL,
|
||||
`status` ENUM('OK', 'WARNING', 'ERROR') NOT NULL DEFAULT 'OK',
|
||||
`nErrors` INTEGER NOT NULL DEFAULT 0,
|
||||
`description` VARCHAR(191) NULL,
|
||||
`image` LONGTEXT NULL,
|
||||
`link` VARCHAR(191) NULL,
|
||||
|
||||
UNIQUE INDEX `Source_URL_key`(`URL`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Tag` (
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
|
||||
PRIMARY KEY (`name`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `_CohortToSource` (
|
||||
`A` INTEGER NOT NULL,
|
||||
`B` INTEGER NOT NULL,
|
||||
|
||||
UNIQUE INDEX `_CohortToSource_AB_unique`(`A`, `B`),
|
||||
INDEX `_CohortToSource_B_index`(`B`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `_PostToTag` (
|
||||
`A` INTEGER NOT NULL,
|
||||
`B` VARCHAR(191) NOT NULL,
|
||||
|
||||
UNIQUE INDEX `_PostToTag_AB_unique`(`A`, `B`),
|
||||
INDEX `_PostToTag_B_index`(`B`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Post` ADD CONSTRAINT `Post_sourceId_fkey` FOREIGN KEY (`sourceId`) REFERENCES `Source`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `_CohortToSource` ADD FOREIGN KEY (`A`) REFERENCES `Cohort`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `_CohortToSource` ADD FOREIGN KEY (`B`) REFERENCES `Source`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `_PostToTag` ADD FOREIGN KEY (`A`) REFERENCES `Post`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `_PostToTag` ADD FOREIGN KEY (`B`) REFERENCES `Tag`(`name`) ON DELETE CASCADE ON UPDATE CASCADE;
|
11
prisma/migrations/20211214141231_minor/migration.sql
Normal file
11
prisma/migrations/20211214141231_minor/migration.sql
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[name]` on the table `Cohort` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE `Cohort` ADD COLUMN `dailyView` INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX `Cohort_name_key` ON `Cohort`(`name`);
|
2
prisma/migrations/20211214141306_summary/migration.sql
Normal file
2
prisma/migrations/20211214141306_summary/migration.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE `Post` MODIFY `summary` TEXT NOT NULL;
|
|
@ -0,0 +1,3 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE `Post` MODIFY `updatedAt` DATETIME(3) NULL,
|
||||
MODIFY `summary` TEXT NULL;
|
|
@ -0,0 +1,5 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE `Post` ADD COLUMN `pippo` VARCHAR(191) NOT NULL DEFAULT '';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE `Source` MODIFY `updatedAt` DATETIME(3) NULL DEFAULT CURRENT_TIMESTAMP(3);
|
9
prisma/migrations/20211216222514_sdf/migration.sql
Normal file
9
prisma/migrations/20211216222514_sdf/migration.sql
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `pippo` on the `Post` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE `Post` DROP COLUMN `pippo`,
|
||||
MODIFY `content` TEXT NULL;
|
2
prisma/migrations/20211217154006_cose/migration.sql
Normal file
2
prisma/migrations/20211217154006_cose/migration.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE `Cohort` ADD COLUMN `description` VARCHAR(191) NULL;
|
17
prisma/migrations/20220120165059_posttag/migration.sql
Normal file
17
prisma/migrations/20220120165059_posttag/migration.sql
Normal file
|
@ -0,0 +1,17 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE `Post` ADD COLUMN `image` VARCHAR(191) NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `_CohortToTag` (
|
||||
`A` INTEGER NOT NULL,
|
||||
`B` VARCHAR(191) NOT NULL,
|
||||
|
||||
UNIQUE INDEX `_CohortToTag_AB_unique`(`A`, `B`),
|
||||
INDEX `_CohortToTag_B_index`(`B`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `_CohortToTag` ADD FOREIGN KEY (`A`) REFERENCES `Cohort`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `_CohortToTag` ADD FOREIGN KEY (`B`) REFERENCES `Tag`(`name`) ON DELETE CASCADE ON UPDATE CASCADE;
|
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "mysql"
|
62
prisma/schema.bk
Normal file
62
prisma/schema.bk
Normal file
|
@ -0,0 +1,62 @@
|
|||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "mysql"
|
||||
url = "mysql://root:diocane@localhost:3306/chewer"
|
||||
}
|
||||
|
||||
// A source to get information from
|
||||
model Source {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
description String?
|
||||
image Json?
|
||||
type SourceType @default(FEED)
|
||||
link String?
|
||||
URL String @unique
|
||||
status Status @default(OK)
|
||||
nErrors Int @default(0)
|
||||
lastError String?
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now())
|
||||
cohorts Cohort[]
|
||||
posts Post[]
|
||||
}
|
||||
|
||||
model Tag {
|
||||
name String @id
|
||||
posts Post[]
|
||||
}
|
||||
|
||||
// generic post
|
||||
model Post {
|
||||
id Int @id @default(autoincrement())
|
||||
date DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
title String
|
||||
URL String @unique
|
||||
content String @db.Text
|
||||
summary String @default("")
|
||||
source Source @relation(fields: [sourceId], references: [id])
|
||||
tags Tag[]
|
||||
sourceId Int
|
||||
}
|
||||
|
||||
model Cohort {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
sources Source[]
|
||||
dailyViews Int? @default(0)
|
||||
}
|
||||
|
||||
enum SourceType {
|
||||
FEED
|
||||
}
|
||||
|
||||
enum Status {
|
||||
OK
|
||||
WARNING
|
||||
ERROR
|
||||
}
|
66
prisma/schema.prisma
Normal file
66
prisma/schema.prisma
Normal file
|
@ -0,0 +1,66 @@
|
|||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "mysql"
|
||||
url = "mysql://root:diocane@localhost:3306/chewer"
|
||||
}
|
||||
|
||||
model Cohort {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
dailyView Int @default(0)
|
||||
description String?
|
||||
sources Source[]
|
||||
tags Tag[]
|
||||
}
|
||||
|
||||
model Post {
|
||||
id Int @id @default(autoincrement())
|
||||
date DateTime @default(now())
|
||||
updatedAt DateTime? @updatedAt
|
||||
title String
|
||||
content String? @db.Text
|
||||
summary String? @db.Text
|
||||
sourceId Int
|
||||
URL String @unique
|
||||
image String?
|
||||
source Source @relation(fields: [sourceId], references: [id])
|
||||
tags Tag[]
|
||||
|
||||
@@index([sourceId], map: "Post_sourceId_fkey")
|
||||
}
|
||||
|
||||
model Source {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
type Source_type @default(FEED)
|
||||
URL String @unique
|
||||
updatedAt DateTime? @default(now())
|
||||
createdAt DateTime @default(now())
|
||||
lastError String?
|
||||
status Source_status @default(OK)
|
||||
nErrors Int @default(0)
|
||||
description String?
|
||||
image String? @db.LongText
|
||||
link String?
|
||||
posts Post[]
|
||||
cohorts Cohort[]
|
||||
}
|
||||
|
||||
model Tag {
|
||||
name String @id
|
||||
cohorts Cohort[]
|
||||
posts Post[]
|
||||
}
|
||||
|
||||
enum Source_type {
|
||||
FEED
|
||||
}
|
||||
|
||||
enum Source_status {
|
||||
OK
|
||||
WARNING
|
||||
ERROR
|
||||
}
|
152
server/chew.js
Normal file
152
server/chew.js
Normal file
|
@ -0,0 +1,152 @@
|
|||
// import p from '@prisma/client'
|
||||
// const { PrismaClient } = p
|
||||
import fetch from 'node-fetch'
|
||||
import FeedParser from 'feedparser'
|
||||
|
||||
// const prisma = new PrismaClient()
|
||||
|
||||
import { getParams, maybeTranslate, parseContent } from './helper.js'
|
||||
|
||||
// let fetch
|
||||
// import('node-fetch').then(f => {
|
||||
// fetch = f
|
||||
// })
|
||||
// import fetch from 'node-fetch'
|
||||
// const FeedParser = require('feedparser')
|
||||
|
||||
// // get('https://cavallette.noblogs.org/feed/atom')
|
||||
const manager = {
|
||||
/**
|
||||
* check if post is new, updated or unknown
|
||||
* @param {Post} post
|
||||
*/
|
||||
async isPostNew (post) {
|
||||
// console.error(post.link)
|
||||
const ret = await prisma.post.findUnique({ where: { URL: post.link } })
|
||||
return !ret
|
||||
},
|
||||
isValid (post) {
|
||||
return true
|
||||
},
|
||||
async sourceError (err, source) {
|
||||
try {
|
||||
await prisma.source.update({ where: { id: source.id }, data: { status: 'WARNING', lastError: String(err) } })
|
||||
} catch (e) {
|
||||
console.error(source, e)
|
||||
}
|
||||
},
|
||||
async sourceCompleted (source) {
|
||||
try {
|
||||
await prisma.source.update({ where: { id: source.id }, data: { status: 'OK', lastError: null }})
|
||||
} catch(e) {
|
||||
console.error(source, e)
|
||||
}
|
||||
},
|
||||
async get (source) {
|
||||
console.error('dentro get!')
|
||||
try {
|
||||
|
||||
// Get a response stream
|
||||
const res = await fetch(source.URL,
|
||||
{
|
||||
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36',
|
||||
'accept': 'text/html,application/xhtml+xml'
|
||||
})
|
||||
|
||||
// Setup feedparser stream
|
||||
const feedparser = new FeedParser()
|
||||
feedparser.on('error', e => manager.sourceError(e, source))
|
||||
feedparser.on('end', e => manager.sourceCompleted(source))
|
||||
feedparser.on('readable', async () => {
|
||||
let post
|
||||
while(post = feedparser.read()) {
|
||||
// validate post
|
||||
if (!manager.isValid(post)) return
|
||||
|
||||
// check if already exist and is not updated
|
||||
if (!await manager.isPostNew(post)) return
|
||||
try {
|
||||
|
||||
// dompurify
|
||||
let { html, image } = parseContent(post.description || post.summary)
|
||||
// console.error(post.enclosures)
|
||||
const enclosuresImages = post.enclosures.filter(e => e.type.includes('image'))
|
||||
image = enclosuresImages.length ? enclosuresImages[0].url : image
|
||||
|
||||
await prisma.post.create({ include: { tags: true }, data: {
|
||||
date: post.pubdate,
|
||||
title: post.title,
|
||||
URL: post.link,
|
||||
content: html,
|
||||
image,
|
||||
summary: post.summary,
|
||||
sourceId: source.id,
|
||||
// tags: {
|
||||
// connect: post.categories.map(name => ({ name })),
|
||||
// create: post.categories.map(name => ({ name }))
|
||||
// // create: { name },
|
||||
// // where: { name }
|
||||
// // }))
|
||||
// // // create: post.categories.map( name => ({ name }))
|
||||
// }
|
||||
}})
|
||||
} catch (e) { console.error(e) }
|
||||
// console.log(post.title)
|
||||
// console.log(JSON.stringify(post, ' ', 4))
|
||||
}
|
||||
})
|
||||
|
||||
// Handle our response and pipe it to feedparser
|
||||
if (res.status !== 200) throw new Error('Bad status code')
|
||||
const charset = getParams(res.headers.get('content-type') || '').charset
|
||||
let responseStream = res.body
|
||||
responseStream = maybeTranslate(responseStream, charset)
|
||||
|
||||
// And boom goes the dynamite
|
||||
responseStream.pipe(feedparser)
|
||||
// res.body.pipe(feedparser)
|
||||
} catch (e) {
|
||||
// console.error('sono qui', e)
|
||||
manager.sourceError(e, source)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// const Queue = require('bull');
|
||||
// import pkg from 'bullmq'
|
||||
import Queue from 'bull'
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
// import fetch from 'node-fetch'
|
||||
// import FeedParser from 'feedparser'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
// import { getParams, maybeTranslate } from './helpers.mjs'
|
||||
|
||||
let queue
|
||||
|
||||
export async function add (s) {
|
||||
queue.add(s, { jobId: s.id, repeat: { every: 10000 } })
|
||||
}
|
||||
|
||||
async function main () {
|
||||
queue = new Queue('foo6', { limiter: { max : 10, duration: 20000 } })
|
||||
console.error('dentro main')
|
||||
queue.clean(1000)
|
||||
await queue.obliterate({ force: true });
|
||||
|
||||
queue.process(job => manager.get(job.data))
|
||||
|
||||
const sources = await prisma.source.findMany()
|
||||
console.error(sources)
|
||||
// sources.forEach(manager.get)
|
||||
// manager.get()
|
||||
// console.error(sources.map(s => s.URL))
|
||||
sources.forEach( s => queue.add(s, { jobId: s.id, repeat: { every: 10000 } }))
|
||||
}
|
||||
|
||||
|
||||
main()
|
||||
// manager.main()
|
103
server/cohort.js
Normal file
103
server/cohort.js
Normal file
|
@ -0,0 +1,103 @@
|
|||
import { PrismaClient } from '@prisma/client'
|
||||
const prisma = new PrismaClient()
|
||||
import accepts from 'accepts'
|
||||
import { Feed } from 'feed'
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
|
||||
const app = express.Router()
|
||||
|
||||
const cohortController = {
|
||||
async create (req, res) {
|
||||
// const { name, sources } = await useBody(req)
|
||||
console.error(req.body)
|
||||
const { name, description, sources } = req.body
|
||||
const cohort = await prisma.cohort.create({ include: { sources: true }, data: {
|
||||
name,
|
||||
description,
|
||||
sources: {
|
||||
connect: sources.map(id => ({ id }))
|
||||
}
|
||||
}})
|
||||
return res.json(cohort)
|
||||
},
|
||||
|
||||
feed (cohort, posts) {
|
||||
const feed = new Feed({
|
||||
title: cohort.name,
|
||||
description: cohort.description,
|
||||
id: cohort.id,
|
||||
// link: cohort.id,
|
||||
generator: 'Chew'
|
||||
})
|
||||
posts.forEach(post => {
|
||||
feed.addItem({
|
||||
title: post.title,
|
||||
id: post.URL,
|
||||
link: post.URL,
|
||||
description: post.summary,
|
||||
content: post.content,
|
||||
image: post.image,
|
||||
date: post.updatedAt
|
||||
})
|
||||
})
|
||||
|
||||
return feed.atom1()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
app.post('/', cohortController.create)
|
||||
|
||||
app.get('/:id\.?:format(json|rss|atom|xml)?', cors(), async (req, res) => {
|
||||
const format = req.params.format || 'json'
|
||||
const id = Number(req.params.id)
|
||||
const maxPosts = Number(req.query.maxPosts) || 10
|
||||
console.error('id', id)
|
||||
const cohort = await prisma.cohort.findUnique({ where: { id }, include: { sources: { select: { id: true }} } })
|
||||
if (!id || !cohort || !cohort.sources) return res.sendStatus(404)
|
||||
|
||||
await prisma.cohort.update({ where: { id }, data: { dailyView: { increment: 1 } } })
|
||||
const posts = await prisma.post.findMany({
|
||||
orderBy: [ { date: 'desc'} ],
|
||||
take: maxPosts,
|
||||
where: {
|
||||
sourceId: {
|
||||
in: cohort.sources.map(s => s.id)
|
||||
}
|
||||
},
|
||||
include: {
|
||||
source: true
|
||||
}
|
||||
})
|
||||
|
||||
const accept = accepts(req)
|
||||
console.error(accept.types())
|
||||
|
||||
switch (format) {
|
||||
case 'xml':
|
||||
case 'rss':
|
||||
return res
|
||||
.contentType('application/rss+xml')
|
||||
// .setHeader('Last-Modified', new Date(cohort.updatedAt).toUTCString())
|
||||
// .set('ETag', Math.round(new Date(cohort.updatedAt).getTime() / 1000))
|
||||
.send(cohortController.feed(cohort, posts))
|
||||
case 'json':
|
||||
default:
|
||||
return res.json({ cohort, posts })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
// app.use((err, req, res, next) => {
|
||||
// console.error('sono asodifaosdijf');
|
||||
// console.error(err);
|
||||
// res.status(500).json({error: 'an error occurred'});
|
||||
// });
|
||||
// if (req.method === 'POST') {
|
||||
// return cohortController.create(req)
|
||||
// }
|
||||
// }
|
||||
|
||||
export default app
|
11
server/cohorts.js
Normal file
11
server/cohorts.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { PrismaClient } from '@prisma/client'
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
import express from 'express'
|
||||
const app = express.Router()
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
res.json(await prisma.cohort.findMany({ take: 10 }))
|
||||
})
|
||||
|
||||
export default app
|
1
server/feed/index.js
Normal file
1
server/feed/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default async () => 'ciao'
|
160
server/helper.js
Normal file
160
server/helper.js
Normal file
|
@ -0,0 +1,160 @@
|
|||
// const iconv = require('iconv-lite')
|
||||
import iconv from 'iconv-lite'
|
||||
import FeedParser from 'feedparser'
|
||||
import { parseHTML } from 'linkedom'
|
||||
import fetch from 'node-fetch'
|
||||
import createDOMPurify from 'dompurify'
|
||||
import { JSDOM } from 'jsdom'
|
||||
|
||||
const window = new JSDOM('').window
|
||||
const DOMPurify = createDOMPurify(window)
|
||||
|
||||
export function getParams (str) {
|
||||
const params = str.split(';').reduce((params, param) => {
|
||||
const parts = param.split('=').map(part => part.trim())
|
||||
if (parts.length === 2) {
|
||||
params[parts[0]] = parts[1]
|
||||
}
|
||||
return params
|
||||
}, {})
|
||||
return params
|
||||
}
|
||||
|
||||
DOMPurify.addHook('beforeSanitizeElements', node => {
|
||||
|
||||
if (node.hasAttribute && node.hasAttribute('href')) {
|
||||
const href = node.getAttribute('href')
|
||||
const text = node.textContent
|
||||
|
||||
// remove FB tracking param
|
||||
if (href.includes('fbclid=')) {
|
||||
try {
|
||||
const url = new URL.URL(href)
|
||||
url.searchParams.delete('fbclid')
|
||||
node.setAttribute('href', url.href)
|
||||
if (text.includes('fbclid=')) {
|
||||
node.textContent = url.href
|
||||
}
|
||||
} catch (e) {
|
||||
return node
|
||||
}
|
||||
}
|
||||
}
|
||||
return node
|
||||
})
|
||||
|
||||
export function parseContent (html) {
|
||||
|
||||
console.error(html)
|
||||
const saneHTML = DOMPurify.sanitize(html, {
|
||||
CUSTOM_ELEMENT_HANDLING: {
|
||||
tagNameCheck: /^(gancio-.*|display-feed)/,
|
||||
attributeNameCheck: /(feed|id|theme)/,
|
||||
allowCustomizedBuiltInElements: true, // allow customized built-ins
|
||||
},
|
||||
ALLOWED_TAGS: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'br', 'i', 'span', 'img', 'figure',
|
||||
'h6', 'b', 'a', 'li', 'ul', 'ol', 'code', 'blockquote', 'u', 's', 'strong'],
|
||||
ALLOWED_ATTR: ['href', 'target', 'src']
|
||||
})
|
||||
console.error(saneHTML)
|
||||
// const images = window.document.getElementsByTagName('img')
|
||||
const { document } = new JSDOM(html).window
|
||||
|
||||
const img = document.querySelector('img[src]')
|
||||
console.error('sono dentro il parsing!')
|
||||
console.error(img)
|
||||
let image
|
||||
if (img) {
|
||||
image = img.getAttribute('src')
|
||||
}
|
||||
|
||||
return { html: saneHTML, image }
|
||||
|
||||
}
|
||||
|
||||
export function maybeTranslate (res, charset) {
|
||||
let iconvStream
|
||||
// Decode using iconv-lite if its not utf8 already.
|
||||
if (!iconvStream && charset && !/utf-*8/i.test(charset)) {
|
||||
try {
|
||||
iconvStream = iconv.decodeStream(charset)
|
||||
console.log('Converting from charset %s to utf-8', charset)
|
||||
// iconvStream.on('error', done)
|
||||
// If we're using iconvStream, stream will be the output of iconvStream
|
||||
// otherwise it will remain the output of request
|
||||
res = res.pipe(iconvStream)
|
||||
} catch(err) {
|
||||
res.emit('error', err)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} URL
|
||||
* @description Check if URL is a valid atom/rss feed or in case it's an html search for a public feed
|
||||
* then retrieve feed detailed information
|
||||
* @returns An object with feed information (title, url)
|
||||
*/
|
||||
export async function getFeedDetails (URL) {
|
||||
// Get a response stream
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0
|
||||
const res = await fetch(URL,
|
||||
{
|
||||
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36',
|
||||
'accept': 'text/html,application/xhtml+xml'
|
||||
})
|
||||
|
||||
// Handle our response and pipe it to feedparser
|
||||
console.error(res.status)
|
||||
if (res.status !== 200) throw new Error('Bad status code')
|
||||
|
||||
const contentType = res.headers.get('content-type')
|
||||
if (contentType.includes('html')) {
|
||||
console.error('parse html')
|
||||
const { document } = parseHTML(await res.text())
|
||||
const links = document.querySelectorAll('link[rel=alternate]')
|
||||
const feeds = []
|
||||
links.forEach(link => {
|
||||
const type = link.getAttribute('type')
|
||||
const href = link.getAttribute('href')
|
||||
if (type && href) {
|
||||
feeds[type] = feeds[type] || href
|
||||
}
|
||||
})
|
||||
console.error(feeds)
|
||||
if (feeds['application/atom+xml']) {
|
||||
return getFeedDetails(feeds['application/atom+xml'])
|
||||
} else if (feeds['application/rss+xml']) {
|
||||
return getFeedDetails(feeds['application/rss+xml'])
|
||||
} else {
|
||||
throw new Error(feeds)
|
||||
}
|
||||
}
|
||||
|
||||
console.error('parse atom feed')
|
||||
|
||||
|
||||
// feedparser.on('error', e => manager.sourceError(e, source))
|
||||
// feedparser.on('end', e => manager.sourceCompleted(source))
|
||||
return new Promise((resolve, reject) => {
|
||||
const feedparser = new FeedParser()
|
||||
feedparser.on('readable', () => {
|
||||
// console.error('sono dentro readable!', feedparser.read())
|
||||
feedparser.meta.URL = URL
|
||||
return resolve(feedparser.meta)
|
||||
})
|
||||
feedparser.on('error', reject)
|
||||
feedparser.on('end', resolve)
|
||||
// Handle our response and pipe it to feedparser
|
||||
const charset = getParams(res.headers.get('content-type') || '').charset
|
||||
console.error('chartset -> ', charset)
|
||||
let responseStream = maybeTranslate(res.body, charset)
|
||||
|
||||
// And boom goes the dynamite
|
||||
responseStream.pipe(feedparser)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// module.exports = { getParams, getFeedDetails, maybeTranslate }
|
35
server/index.js
Normal file
35
server/index.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import express from 'express'
|
||||
import bodyParser from 'body-parser'
|
||||
import cohort from './cohort.js'
|
||||
import cohorts from './cohorts.js'
|
||||
import posts from './posts.js'
|
||||
import source from './source.js'
|
||||
|
||||
const app = express()
|
||||
app.use((req, res, next) => {
|
||||
console.error(req.path)
|
||||
next()
|
||||
})
|
||||
|
||||
const api = express.Router()
|
||||
api.use(bodyParser.json())
|
||||
app.use('/api', api)
|
||||
api.use('/cohort', cohort)
|
||||
api.use('/cohorts', cohorts)
|
||||
api.use('/posts', posts)
|
||||
api.use('/source', source)
|
||||
|
||||
// app.use((req, res, next) => {
|
||||
// console.error('404!')
|
||||
// return res.status(404).json(err)
|
||||
// next()
|
||||
// })
|
||||
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('sono asodifaosdijf');
|
||||
console.error(err.stack);
|
||||
// return res.status(500).json({error: 'an error occurred'});
|
||||
})
|
||||
|
||||
import './chew'
|
||||
export default app
|
16
server/initialize.server.js
Normal file
16
server/initialize.server.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
|
||||
export default async function () {
|
||||
async function start (nuxt) {
|
||||
|
||||
// close connections/port/unix socket
|
||||
async function shutdown () {
|
||||
process.off('SIGTERM', shutdown)
|
||||
process.off('SIGINT', shutdown)
|
||||
nuxt.close()
|
||||
process.exit()
|
||||
}
|
||||
process.on('SIGTERM', shutdown)
|
||||
process.on('SIGINT', shutdown)
|
||||
}
|
||||
this.nuxt.hook('listen', start)
|
||||
}
|
10
server/posts.js
Normal file
10
server/posts.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { PrismaClient } from '@prisma/client'
|
||||
const prisma = new PrismaClient()
|
||||
import express from 'express'
|
||||
const app = express.Router()
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
res.json(await prisma.post.findMany({ take: 10, orderBy: { date: 'desc' }, include: { source: true }}))
|
||||
})
|
||||
|
||||
export default app
|
152
server/source.js
Normal file
152
server/source.js
Normal file
|
@ -0,0 +1,152 @@
|
|||
import { PrismaClient } from '@prisma/client'
|
||||
const prisma = new PrismaClient()
|
||||
// import { useBody } from 'h3'
|
||||
import { getFeedDetails } from './helper.js'
|
||||
import cors from 'cors'
|
||||
import express from 'express'
|
||||
import { Feed } from 'feed'
|
||||
|
||||
import { add } from './chew'
|
||||
|
||||
const app = express.Router()
|
||||
|
||||
const sourceController = {
|
||||
feed (source, posts) {
|
||||
const feed = new Feed({
|
||||
title: source.name,
|
||||
description: source.description,
|
||||
id: source.id,
|
||||
// link: source.id,
|
||||
generator: 'Chew'
|
||||
})
|
||||
posts.forEach(post => {
|
||||
feed.addItem({
|
||||
title: post.title,
|
||||
id: post.URL,
|
||||
link: post.URL,
|
||||
description: post.summary,
|
||||
content: post.content,
|
||||
image: post.image,
|
||||
date: post.updatedAt
|
||||
})
|
||||
})
|
||||
|
||||
return feed.atom1()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/search', async (req, res) => {
|
||||
const search = req.query.search || ''
|
||||
console.error('sono dentro search', search)
|
||||
const sources = await prisma.source.findMany({ where: {
|
||||
OR: [
|
||||
{ name: { contains: search } },
|
||||
{ URL: { contains: search } }
|
||||
]
|
||||
}})
|
||||
return res.json(sources)
|
||||
})
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
const sources = await prisma.source.findMany()
|
||||
return res.json(sources)
|
||||
})
|
||||
|
||||
const corsOptions = { exposedHeaders: ['Accept-Language',
|
||||
'Access-Control-Allow-Origin',
|
||||
'Connection', 'Content-Length', 'Content-Type', 'Date',
|
||||
'Etag', 'Server', 'Via', 'X-Powered-By']
|
||||
}
|
||||
|
||||
app.get('/:id\.?:format(json|rss|atom|xml)?', cors(corsOptions), async (req, res) => {
|
||||
const format = req.params.format || 'json'
|
||||
const sourceId = Number(req.params.id)
|
||||
const maxPosts = Number(req.query.maxPosts) || 10
|
||||
const source = await prisma.source.findUnique({ where: { id: sourceId }})
|
||||
if (!sourceId || !source) return res.sendStatus(404)
|
||||
|
||||
// await prisma..update({ where: { id }, data: { dailyView: { increment: 1 } } })
|
||||
const posts = await prisma.post.findMany({
|
||||
orderBy: [ { date: 'desc'} ],
|
||||
take: maxPosts,
|
||||
where: {
|
||||
sourceId
|
||||
},
|
||||
include: {
|
||||
source: true
|
||||
}
|
||||
})
|
||||
|
||||
// const accept = accepts(req)
|
||||
// console.error(accept.types())
|
||||
|
||||
switch (format) {
|
||||
case 'xml':
|
||||
case 'rss':
|
||||
case 'atom':
|
||||
return res
|
||||
.contentType('application/rss+xml')
|
||||
.setHeader('Last-Modified', new Date(source.updatedAt).toUTCString())
|
||||
.set('ETag', Math.round(new Date(source.updatedAt).getTime() / 1000))
|
||||
.send(sourceController.feed(source, posts))
|
||||
case 'json':
|
||||
default:
|
||||
|
||||
return res.json({ source, posts })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
app.post('/', async (req, res) => {
|
||||
const URL = req.body.URL
|
||||
// const URL = req.query.URL || req.params.URL || req.body.URL
|
||||
// return
|
||||
// check if URL already exists
|
||||
let dbsource = await prisma.source.findFirst( {
|
||||
where: { URL },
|
||||
include: {
|
||||
_count: {
|
||||
select: { posts: true }
|
||||
}
|
||||
}
|
||||
})
|
||||
if (dbsource) return res.json(dbsource)
|
||||
let source
|
||||
try {
|
||||
source = await getFeedDetails(URL)
|
||||
} catch (e) {
|
||||
console.error(String(e))
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
if (!source) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
dbsource = await prisma.source.findFirst( {
|
||||
where: { URL: source.URL },
|
||||
include: {
|
||||
_count: {
|
||||
select: { posts: true }
|
||||
}
|
||||
}
|
||||
})
|
||||
if (dbsource) return res.json(dbsource)
|
||||
const ret = await prisma.source.create({ data: {
|
||||
name: source.title,
|
||||
description: source.description,
|
||||
URL: source.URL,
|
||||
link: source.link,
|
||||
updatedAt: source.date || undefined,
|
||||
// image: source.image
|
||||
}})
|
||||
add(ret)
|
||||
return res.json(ret)
|
||||
|
||||
// return prisma.cohort.findMany({ take: 10 })
|
||||
// return { status: 'OK'}
|
||||
})
|
||||
|
||||
|
||||
export default app
|
655
static/display-feed.js
Normal file
655
static/display-feed.js
Normal file
|
@ -0,0 +1,655 @@
|
|||
function noop() {
|
||||
}
|
||||
function run(fn) {
|
||||
return fn();
|
||||
}
|
||||
function blank_object() {
|
||||
return Object.create(null);
|
||||
}
|
||||
function run_all(fns) {
|
||||
fns.forEach(run);
|
||||
}
|
||||
function is_function(thing) {
|
||||
return typeof thing === "function";
|
||||
}
|
||||
function safe_not_equal(a, b) {
|
||||
return a != a ? b == b : a !== b || (a && typeof a === "object" || typeof a === "function");
|
||||
}
|
||||
let src_url_equal_anchor;
|
||||
function src_url_equal(element_src, url) {
|
||||
if (!src_url_equal_anchor) {
|
||||
src_url_equal_anchor = document.createElement("a");
|
||||
}
|
||||
src_url_equal_anchor.href = url;
|
||||
return element_src === src_url_equal_anchor.href;
|
||||
}
|
||||
function is_empty(obj) {
|
||||
return Object.keys(obj).length === 0;
|
||||
}
|
||||
function append(target, node) {
|
||||
target.appendChild(node);
|
||||
}
|
||||
function insert(target, node, anchor) {
|
||||
target.insertBefore(node, anchor || null);
|
||||
}
|
||||
function detach(node) {
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
function destroy_each(iterations, detaching) {
|
||||
for (let i = 0; i < iterations.length; i += 1) {
|
||||
if (iterations[i])
|
||||
iterations[i].d(detaching);
|
||||
}
|
||||
}
|
||||
function element(name) {
|
||||
return document.createElement(name);
|
||||
}
|
||||
function text(data) {
|
||||
return document.createTextNode(data);
|
||||
}
|
||||
function space() {
|
||||
return text(" ");
|
||||
}
|
||||
function empty() {
|
||||
return text("");
|
||||
}
|
||||
function attr(node, attribute, value) {
|
||||
if (value == null)
|
||||
node.removeAttribute(attribute);
|
||||
else if (node.getAttribute(attribute) !== value)
|
||||
node.setAttribute(attribute, value);
|
||||
}
|
||||
function children(element2) {
|
||||
return Array.from(element2.childNodes);
|
||||
}
|
||||
function set_data(text2, data) {
|
||||
data = "" + data;
|
||||
if (text2.wholeText !== data)
|
||||
text2.data = data;
|
||||
}
|
||||
function attribute_to_object(attributes) {
|
||||
const result = {};
|
||||
for (const attribute of attributes) {
|
||||
result[attribute.name] = attribute.value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
let current_component;
|
||||
function set_current_component(component) {
|
||||
current_component = component;
|
||||
}
|
||||
function get_current_component() {
|
||||
if (!current_component)
|
||||
throw new Error("Function called outside component initialization");
|
||||
return current_component;
|
||||
}
|
||||
function onMount(fn) {
|
||||
get_current_component().$$.on_mount.push(fn);
|
||||
}
|
||||
const dirty_components = [];
|
||||
const binding_callbacks = [];
|
||||
const render_callbacks = [];
|
||||
const flush_callbacks = [];
|
||||
const resolved_promise = Promise.resolve();
|
||||
let update_scheduled = false;
|
||||
function schedule_update() {
|
||||
if (!update_scheduled) {
|
||||
update_scheduled = true;
|
||||
resolved_promise.then(flush);
|
||||
}
|
||||
}
|
||||
function add_render_callback(fn) {
|
||||
render_callbacks.push(fn);
|
||||
}
|
||||
let flushing = false;
|
||||
const seen_callbacks = new Set();
|
||||
function flush() {
|
||||
if (flushing)
|
||||
return;
|
||||
flushing = true;
|
||||
do {
|
||||
for (let i = 0; i < dirty_components.length; i += 1) {
|
||||
const component = dirty_components[i];
|
||||
set_current_component(component);
|
||||
update(component.$$);
|
||||
}
|
||||
set_current_component(null);
|
||||
dirty_components.length = 0;
|
||||
while (binding_callbacks.length)
|
||||
binding_callbacks.pop()();
|
||||
for (let i = 0; i < render_callbacks.length; i += 1) {
|
||||
const callback = render_callbacks[i];
|
||||
if (!seen_callbacks.has(callback)) {
|
||||
seen_callbacks.add(callback);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
render_callbacks.length = 0;
|
||||
} while (dirty_components.length);
|
||||
while (flush_callbacks.length) {
|
||||
flush_callbacks.pop()();
|
||||
}
|
||||
update_scheduled = false;
|
||||
flushing = false;
|
||||
seen_callbacks.clear();
|
||||
}
|
||||
function update($$) {
|
||||
if ($$.fragment !== null) {
|
||||
$$.update();
|
||||
run_all($$.before_update);
|
||||
const dirty = $$.dirty;
|
||||
$$.dirty = [-1];
|
||||
$$.fragment && $$.fragment.p($$.ctx, dirty);
|
||||
$$.after_update.forEach(add_render_callback);
|
||||
}
|
||||
}
|
||||
const outroing = new Set();
|
||||
function transition_in(block, local) {
|
||||
if (block && block.i) {
|
||||
outroing.delete(block);
|
||||
block.i(local);
|
||||
}
|
||||
}
|
||||
function mount_component(component, target, anchor, customElement) {
|
||||
const { fragment, on_mount, on_destroy, after_update } = component.$$;
|
||||
fragment && fragment.m(target, anchor);
|
||||
if (!customElement) {
|
||||
add_render_callback(() => {
|
||||
const new_on_destroy = on_mount.map(run).filter(is_function);
|
||||
if (on_destroy) {
|
||||
on_destroy.push(...new_on_destroy);
|
||||
} else {
|
||||
run_all(new_on_destroy);
|
||||
}
|
||||
component.$$.on_mount = [];
|
||||
});
|
||||
}
|
||||
after_update.forEach(add_render_callback);
|
||||
}
|
||||
function destroy_component(component, detaching) {
|
||||
const $$ = component.$$;
|
||||
if ($$.fragment !== null) {
|
||||
run_all($$.on_destroy);
|
||||
$$.fragment && $$.fragment.d(detaching);
|
||||
$$.on_destroy = $$.fragment = null;
|
||||
$$.ctx = [];
|
||||
}
|
||||
}
|
||||
function make_dirty(component, i) {
|
||||
if (component.$$.dirty[0] === -1) {
|
||||
dirty_components.push(component);
|
||||
schedule_update();
|
||||
component.$$.dirty.fill(0);
|
||||
}
|
||||
component.$$.dirty[i / 31 | 0] |= 1 << i % 31;
|
||||
}
|
||||
function init(component, options, instance2, create_fragment2, not_equal, props, append_styles, dirty = [-1]) {
|
||||
const parent_component = current_component;
|
||||
set_current_component(component);
|
||||
const $$ = component.$$ = {
|
||||
fragment: null,
|
||||
ctx: null,
|
||||
props,
|
||||
update: noop,
|
||||
not_equal,
|
||||
bound: blank_object(),
|
||||
on_mount: [],
|
||||
on_destroy: [],
|
||||
on_disconnect: [],
|
||||
before_update: [],
|
||||
after_update: [],
|
||||
context: new Map(options.context || (parent_component ? parent_component.$$.context : [])),
|
||||
callbacks: blank_object(),
|
||||
dirty,
|
||||
skip_bound: false,
|
||||
root: options.target || parent_component.$$.root
|
||||
};
|
||||
append_styles && append_styles($$.root);
|
||||
let ready = false;
|
||||
$$.ctx = instance2 ? instance2(component, options.props || {}, (i, ret, ...rest) => {
|
||||
const value = rest.length ? rest[0] : ret;
|
||||
if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {
|
||||
if (!$$.skip_bound && $$.bound[i])
|
||||
$$.bound[i](value);
|
||||
if (ready)
|
||||
make_dirty(component, i);
|
||||
}
|
||||
return ret;
|
||||
}) : [];
|
||||
$$.update();
|
||||
ready = true;
|
||||
run_all($$.before_update);
|
||||
$$.fragment = create_fragment2 ? create_fragment2($$.ctx) : false;
|
||||
if (options.target) {
|
||||
if (options.hydrate) {
|
||||
const nodes = children(options.target);
|
||||
$$.fragment && $$.fragment.l(nodes);
|
||||
nodes.forEach(detach);
|
||||
} else {
|
||||
$$.fragment && $$.fragment.c();
|
||||
}
|
||||
if (options.intro)
|
||||
transition_in(component.$$.fragment);
|
||||
mount_component(component, options.target, options.anchor, options.customElement);
|
||||
flush();
|
||||
}
|
||||
set_current_component(parent_component);
|
||||
}
|
||||
let SvelteElement;
|
||||
if (typeof HTMLElement === "function") {
|
||||
SvelteElement = class extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: "open" });
|
||||
}
|
||||
connectedCallback() {
|
||||
const { on_mount } = this.$$;
|
||||
this.$$.on_disconnect = on_mount.map(run).filter(is_function);
|
||||
for (const key in this.$$.slotted) {
|
||||
this.appendChild(this.$$.slotted[key]);
|
||||
}
|
||||
}
|
||||
attributeChangedCallback(attr2, _oldValue, newValue) {
|
||||
this[attr2] = newValue;
|
||||
}
|
||||
disconnectedCallback() {
|
||||
run_all(this.$$.on_disconnect);
|
||||
}
|
||||
$destroy() {
|
||||
destroy_component(this, 1);
|
||||
this.$destroy = noop;
|
||||
}
|
||||
$on(type, callback) {
|
||||
const callbacks = this.$$.callbacks[type] || (this.$$.callbacks[type] = []);
|
||||
callbacks.push(callback);
|
||||
return () => {
|
||||
const index = callbacks.indexOf(callback);
|
||||
if (index !== -1)
|
||||
callbacks.splice(index, 1);
|
||||
};
|
||||
}
|
||||
$set($$props) {
|
||||
if (this.$$set && !is_empty($$props)) {
|
||||
this.$$.skip_bound = true;
|
||||
this.$$set($$props);
|
||||
this.$$.skip_bound = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
function get_each_context(ctx, list, i) {
|
||||
const child_ctx = ctx.slice();
|
||||
child_ctx[7] = list[i];
|
||||
return child_ctx;
|
||||
}
|
||||
function create_if_block(ctx) {
|
||||
let div;
|
||||
let t;
|
||||
let if_block = ctx[0] && create_if_block_2(ctx);
|
||||
let each_value = ctx[2];
|
||||
let each_blocks = [];
|
||||
for (let i = 0; i < each_value.length; i += 1) {
|
||||
each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i));
|
||||
}
|
||||
return {
|
||||
c() {
|
||||
div = element("div");
|
||||
if (if_block)
|
||||
if_block.c();
|
||||
t = space();
|
||||
for (let i = 0; i < each_blocks.length; i += 1) {
|
||||
each_blocks[i].c();
|
||||
}
|
||||
attr(div, "id", "displayFeed");
|
||||
},
|
||||
m(target, anchor) {
|
||||
insert(target, div, anchor);
|
||||
if (if_block)
|
||||
if_block.m(div, null);
|
||||
append(div, t);
|
||||
for (let i = 0; i < each_blocks.length; i += 1) {
|
||||
each_blocks[i].m(div, null);
|
||||
}
|
||||
},
|
||||
p(ctx2, dirty) {
|
||||
if (ctx2[0]) {
|
||||
if (if_block) {
|
||||
if_block.p(ctx2, dirty);
|
||||
} else {
|
||||
if_block = create_if_block_2(ctx2);
|
||||
if_block.c();
|
||||
if_block.m(div, t);
|
||||
}
|
||||
} else if (if_block) {
|
||||
if_block.d(1);
|
||||
if_block = null;
|
||||
}
|
||||
if (dirty & 6) {
|
||||
each_value = ctx2[2];
|
||||
let i;
|
||||
for (i = 0; i < each_value.length; i += 1) {
|
||||
const child_ctx = get_each_context(ctx2, each_value, i);
|
||||
if (each_blocks[i]) {
|
||||
each_blocks[i].p(child_ctx, dirty);
|
||||
} else {
|
||||
each_blocks[i] = create_each_block(child_ctx);
|
||||
each_blocks[i].c();
|
||||
each_blocks[i].m(div, null);
|
||||
}
|
||||
}
|
||||
for (; i < each_blocks.length; i += 1) {
|
||||
each_blocks[i].d(1);
|
||||
}
|
||||
each_blocks.length = each_value.length;
|
||||
}
|
||||
},
|
||||
d(detaching) {
|
||||
if (detaching)
|
||||
detach(div);
|
||||
if (if_block)
|
||||
if_block.d();
|
||||
destroy_each(each_blocks, detaching);
|
||||
}
|
||||
};
|
||||
}
|
||||
function create_if_block_2(ctx) {
|
||||
let div;
|
||||
let span;
|
||||
let t;
|
||||
return {
|
||||
c() {
|
||||
div = element("div");
|
||||
span = element("span");
|
||||
t = text(ctx[0]);
|
||||
attr(span, "id", "headerTitle");
|
||||
attr(div, "class", "content");
|
||||
},
|
||||
m(target, anchor) {
|
||||
insert(target, div, anchor);
|
||||
append(div, span);
|
||||
append(span, t);
|
||||
},
|
||||
p(ctx2, dirty) {
|
||||
if (dirty & 1)
|
||||
set_data(t, ctx2[0]);
|
||||
},
|
||||
d(detaching) {
|
||||
if (detaching)
|
||||
detach(div);
|
||||
}
|
||||
};
|
||||
}
|
||||
function create_if_block_1(ctx) {
|
||||
let div;
|
||||
let raw_value = ctx[7].summary + "";
|
||||
return {
|
||||
c() {
|
||||
div = element("div");
|
||||
attr(div, "class", "summary");
|
||||
},
|
||||
m(target, anchor) {
|
||||
insert(target, div, anchor);
|
||||
div.innerHTML = raw_value;
|
||||
},
|
||||
p(ctx2, dirty) {
|
||||
if (dirty & 4 && raw_value !== (raw_value = ctx2[7].summary + ""))
|
||||
div.innerHTML = raw_value;
|
||||
},
|
||||
d(detaching) {
|
||||
if (detaching)
|
||||
detach(div);
|
||||
}
|
||||
};
|
||||
}
|
||||
function create_each_block(ctx) {
|
||||
let a;
|
||||
let div0;
|
||||
let img;
|
||||
let img_src_value;
|
||||
let img_alt_value;
|
||||
let t0;
|
||||
let div4;
|
||||
let div1;
|
||||
let t1_value = ctx[7].source.name + "";
|
||||
let t1;
|
||||
let t2;
|
||||
let div2;
|
||||
let t3_value = ctx[7].title + "";
|
||||
let t3;
|
||||
let t4;
|
||||
let div3;
|
||||
let t5_value = when(ctx[7].date) + "";
|
||||
let t5;
|
||||
let t6;
|
||||
let t7;
|
||||
let a_href_value;
|
||||
let a_title_value;
|
||||
let if_block = ctx[1] === "true" && create_if_block_1(ctx);
|
||||
return {
|
||||
c() {
|
||||
a = element("a");
|
||||
div0 = element("div");
|
||||
img = element("img");
|
||||
t0 = space();
|
||||
div4 = element("div");
|
||||
div1 = element("div");
|
||||
t1 = text(t1_value);
|
||||
t2 = space();
|
||||
div2 = element("div");
|
||||
t3 = text(t3_value);
|
||||
t4 = space();
|
||||
div3 = element("div");
|
||||
t5 = text(t5_value);
|
||||
t6 = space();
|
||||
if (if_block)
|
||||
if_block.c();
|
||||
t7 = space();
|
||||
if (!src_url_equal(img.src, img_src_value = ctx[7].image))
|
||||
attr(img, "src", img_src_value);
|
||||
attr(img, "alt", img_alt_value = ctx[7].title);
|
||||
attr(div0, "class", "media");
|
||||
attr(div1, "class", "subtitle");
|
||||
attr(div2, "class", "title");
|
||||
attr(div3, "class", "subtitle");
|
||||
attr(div4, "class", "content");
|
||||
attr(a, "href", a_href_value = ctx[7].URL);
|
||||
attr(a, "title", a_title_value = ctx[7].title);
|
||||
attr(a, "class", "post");
|
||||
attr(a, "target", "_blank");
|
||||
},
|
||||
m(target, anchor) {
|
||||
insert(target, a, anchor);
|
||||
append(a, div0);
|
||||
append(div0, img);
|
||||
append(a, t0);
|
||||
append(a, div4);
|
||||
append(div4, div1);
|
||||
append(div1, t1);
|
||||
append(div4, t2);
|
||||
append(div4, div2);
|
||||
append(div2, t3);
|
||||
append(div4, t4);
|
||||
append(div4, div3);
|
||||
append(div3, t5);
|
||||
append(div4, t6);
|
||||
if (if_block)
|
||||
if_block.m(div4, null);
|
||||
append(a, t7);
|
||||
},
|
||||
p(ctx2, dirty) {
|
||||
if (dirty & 4 && !src_url_equal(img.src, img_src_value = ctx2[7].image)) {
|
||||
attr(img, "src", img_src_value);
|
||||
}
|
||||
if (dirty & 4 && img_alt_value !== (img_alt_value = ctx2[7].title)) {
|
||||
attr(img, "alt", img_alt_value);
|
||||
}
|
||||
if (dirty & 4 && t1_value !== (t1_value = ctx2[7].source.name + ""))
|
||||
set_data(t1, t1_value);
|
||||
if (dirty & 4 && t3_value !== (t3_value = ctx2[7].title + ""))
|
||||
set_data(t3, t3_value);
|
||||
if (dirty & 4 && t5_value !== (t5_value = when(ctx2[7].date) + ""))
|
||||
set_data(t5, t5_value);
|
||||
if (ctx2[1] === "true") {
|
||||
if (if_block) {
|
||||
if_block.p(ctx2, dirty);
|
||||
} else {
|
||||
if_block = create_if_block_1(ctx2);
|
||||
if_block.c();
|
||||
if_block.m(div4, null);
|
||||
}
|
||||
} else if (if_block) {
|
||||
if_block.d(1);
|
||||
if_block = null;
|
||||
}
|
||||
if (dirty & 4 && a_href_value !== (a_href_value = ctx2[7].URL)) {
|
||||
attr(a, "href", a_href_value);
|
||||
}
|
||||
if (dirty & 4 && a_title_value !== (a_title_value = ctx2[7].title)) {
|
||||
attr(a, "title", a_title_value);
|
||||
}
|
||||
},
|
||||
d(detaching) {
|
||||
if (detaching)
|
||||
detach(a);
|
||||
if (if_block)
|
||||
if_block.d();
|
||||
}
|
||||
};
|
||||
}
|
||||
function create_fragment(ctx) {
|
||||
let if_block_anchor;
|
||||
let if_block = ctx[2].length && create_if_block(ctx);
|
||||
return {
|
||||
c() {
|
||||
if (if_block)
|
||||
if_block.c();
|
||||
if_block_anchor = empty();
|
||||
this.c = noop;
|
||||
},
|
||||
m(target, anchor) {
|
||||
if (if_block)
|
||||
if_block.m(target, anchor);
|
||||
insert(target, if_block_anchor, anchor);
|
||||
},
|
||||
p(ctx2, [dirty]) {
|
||||
if (ctx2[2].length) {
|
||||
if (if_block) {
|
||||
if_block.p(ctx2, dirty);
|
||||
} else {
|
||||
if_block = create_if_block(ctx2);
|
||||
if_block.c();
|
||||
if_block.m(if_block_anchor.parentNode, if_block_anchor);
|
||||
}
|
||||
} else if (if_block) {
|
||||
if_block.d(1);
|
||||
if_block = null;
|
||||
}
|
||||
},
|
||||
i: noop,
|
||||
o: noop,
|
||||
d(detaching) {
|
||||
if (if_block)
|
||||
if_block.d(detaching);
|
||||
if (detaching)
|
||||
detach(if_block_anchor);
|
||||
}
|
||||
};
|
||||
}
|
||||
function when(timestamp) {
|
||||
return new Date(timestamp).toLocaleDateString(void 0, {
|
||||
weekday: "long",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit"
|
||||
});
|
||||
}
|
||||
function instance($$self, $$props, $$invalidate) {
|
||||
let { feed = "" } = $$props;
|
||||
let { title = "Feed" } = $$props;
|
||||
let { max = false } = $$props;
|
||||
let { summary = "true" } = $$props;
|
||||
let mounted = false;
|
||||
let posts = [];
|
||||
function update2(v) {
|
||||
if (!mounted)
|
||||
return;
|
||||
fetch(feed).then((res) => res.json()).then((e) => {
|
||||
$$invalidate(2, posts = e.posts);
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
onMount(() => {
|
||||
mounted = true;
|
||||
update2();
|
||||
});
|
||||
$$self.$$set = ($$props2) => {
|
||||
if ("feed" in $$props2)
|
||||
$$invalidate(3, feed = $$props2.feed);
|
||||
if ("title" in $$props2)
|
||||
$$invalidate(0, title = $$props2.title);
|
||||
if ("max" in $$props2)
|
||||
$$invalidate(4, max = $$props2.max);
|
||||
if ("summary" in $$props2)
|
||||
$$invalidate(1, summary = $$props2.summary);
|
||||
};
|
||||
$$self.$$.update = () => {
|
||||
if ($$self.$$.dirty & 25) {
|
||||
update2();
|
||||
}
|
||||
};
|
||||
return [title, summary, posts, feed, max];
|
||||
}
|
||||
class DisplayFeed extends SvelteElement {
|
||||
constructor(options) {
|
||||
super();
|
||||
this.shadowRoot.innerHTML = `<style>#displayFeed{font-family:'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;overflow-x:hidden;font-size:1.2rem;width:100%;box-sizing:content-box}#headerTitle{line-height:45px;font-size:1.3rem;font-weight:600}.post{display:flex;flex-direction:row;flex-wrap:wrap;width:100%;padding:0px;color:black;text-decoration:none;transition:background .3s ease-in-out;border-radius:5px;;}.post:hover{background-color:#ececec}.post .media,.post .content{padding:10px;display:flex;flex-direction:column;flex-basis:100%;flex:1}.post .content{flex:3}.post .content .title{font-size:1.5rem;font-weight:700;color:black}.post .content .subtitle{font-weight:100;color:#666;font-size:.9rem}.post .content .summary{margin-top:1rem;color:#333;font-size:1rem}.post .media img{border-radius:5px;flex:1;max-height:250px;width:400px;object-fit:cover}</style>`;
|
||||
init(this, {
|
||||
target: this.shadowRoot,
|
||||
props: attribute_to_object(this.attributes),
|
||||
customElement: true
|
||||
}, instance, create_fragment, safe_not_equal, { feed: 3, title: 0, max: 4, summary: 1 }, null);
|
||||
if (options) {
|
||||
if (options.target) {
|
||||
insert(options.target, this, options.anchor);
|
||||
}
|
||||
if (options.props) {
|
||||
this.$set(options.props);
|
||||
flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
static get observedAttributes() {
|
||||
return ["feed", "title", "max", "summary"];
|
||||
}
|
||||
get feed() {
|
||||
return this.$$.ctx[3];
|
||||
}
|
||||
set feed(feed) {
|
||||
this.$$set({ feed });
|
||||
flush();
|
||||
}
|
||||
get title() {
|
||||
return this.$$.ctx[0];
|
||||
}
|
||||
set title(title) {
|
||||
this.$$set({ title });
|
||||
flush();
|
||||
}
|
||||
get max() {
|
||||
return this.$$.ctx[4];
|
||||
}
|
||||
set max(max) {
|
||||
this.$$set({ max });
|
||||
flush();
|
||||
}
|
||||
get summary() {
|
||||
return this.$$.ctx[1];
|
||||
}
|
||||
set summary(summary) {
|
||||
this.$$set({ summary });
|
||||
flush();
|
||||
}
|
||||
}
|
||||
customElements.define("display-feed", DisplayFeed);
|
55
static/display-feed.noshadow.js
Normal file
55
static/display-feed.noshadow.js
Normal file
File diff suppressed because one or more lines are too long
BIN
static/favicon.ico
Normal file
BIN
static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.4 KiB |
BIN
static/v.png
Normal file
BIN
static/v.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
1
static/vuetify-logo.svg
Normal file
1
static/vuetify-logo.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>
|
After Width: | Height: | Size: 539 B |
10
store/README.md
Normal file
10
store/README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# STORE
|
||||
|
||||
**This directory is not required, you can delete it if you don't want to use it.**
|
||||
|
||||
This directory contains your Vuex Store files.
|
||||
Vuex Store option is implemented in the Nuxt.js framework.
|
||||
|
||||
Creating a file in this directory automatically activates the option in the framework.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).
|
4
webcomponents/.gitignore
vendored
Normal file
4
webcomponents/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
/node_modules/
|
||||
/dist/
|
||||
/.vscode/
|
||||
.DS_Store
|
48
webcomponents/README.md
Normal file
48
webcomponents/README.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Svelte + Vite
|
||||
|
||||
This template should help get you started developing with Svelte in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
|
||||
|
||||
## Need an official Svelte framework?
|
||||
|
||||
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
|
||||
|
||||
## Technical considerations
|
||||
|
||||
**Why use this over SvelteKit?**
|
||||
|
||||
- It brings its own routing solution which might not be preferable for some users.
|
||||
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
|
||||
`vite dev` and `vite build` wouldn't work in a SvelteKit environment, for example.
|
||||
|
||||
This template contains as little as possible to get started with Vite + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
|
||||
|
||||
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
|
||||
|
||||
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
|
||||
|
||||
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
|
||||
|
||||
**Why include `.vscode/extensions.json`?**
|
||||
|
||||
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
|
||||
|
||||
**Why enable `checkJs` in the JS template?**
|
||||
|
||||
It is likely that most cases of changing variable types in runtime are likely to be accidental, rather than deliberate. This provides advanced typechecking out of the box. Should you like to take advantage of the dynamically-typed nature of JavaScript, it is trivial to change the configuration.
|
||||
|
||||
**Why is HMR not preserving my local component state?**
|
||||
|
||||
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
|
||||
|
||||
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
|
||||
|
||||
```js
|
||||
// store.js
|
||||
// An extremely simple external store
|
||||
import { writable } from 'svelte/store'
|
||||
export default writable(0)
|
||||
```
|
14
webcomponents/index.html
Normal file
14
webcomponents/index.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Display Feed</title>
|
||||
</head>
|
||||
<body>
|
||||
<display-feed
|
||||
feed='http://localhost:3000/api/source/2' title='Feed'></display-feed>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
34
webcomponents/jsconfig.json
Normal file
34
webcomponents/jsconfig.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
/**
|
||||
* svelte-preprocess cannot figure out whether you have
|
||||
* a value or a type, so tell TypeScript to enforce using
|
||||
* `import type` instead of `import` for Types.
|
||||
*/
|
||||
"importsNotUsedAsValues": "error",
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* To have warnings / errors of the Svelte compiler at the
|
||||
* correct position, enable source maps by default.
|
||||
*/
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"baseUrl": ".",
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable this if you'd like to use dynamic types.
|
||||
*/
|
||||
"checkJs": true
|
||||
},
|
||||
/**
|
||||
* Use global.d.ts instead of compilerOptions.types
|
||||
* to avoid limiting type declarations.
|
||||
*/
|
||||
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
}
|
19
webcomponents/package.json
Normal file
19
webcomponents/package.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "display-feed",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build:lib": "vite build -c=vite.lib.config.js",
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.11",
|
||||
"svelte": "^3.37.0",
|
||||
"vite": "^2.6.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"twind": "^0.16.16"
|
||||
}
|
||||
}
|
154
webcomponents/src/DisplayFeed.svelte
Normal file
154
webcomponents/src/DisplayFeed.svelte
Normal file
|
@ -0,0 +1,154 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
export let feed = ''
|
||||
export let title = 'Feed'
|
||||
export let max = false
|
||||
export let summary = 'true'
|
||||
// export let tags = ''
|
||||
|
||||
let mounted = false
|
||||
let posts = []
|
||||
|
||||
function update (v) {
|
||||
if (!mounted) return
|
||||
const params = []
|
||||
if (max) {
|
||||
params.push(`max=${max}`)
|
||||
}
|
||||
|
||||
fetch(feed)
|
||||
.then(res => res.json())
|
||||
.then(e => {
|
||||
posts = e.posts
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function when (timestamp) {
|
||||
return new Date(timestamp)
|
||||
.toLocaleDateString(undefined,
|
||||
{
|
||||
weekday: 'long',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
onMount(() => {
|
||||
mounted = true
|
||||
update()
|
||||
})
|
||||
$: update(feed && max && title)
|
||||
|
||||
</script>
|
||||
<svelte:options tag="display-feed"/>
|
||||
{#if posts.length}
|
||||
<div id='displayFeed'>
|
||||
{#if title}
|
||||
<div class='content'>
|
||||
<span id='headerTitle'>{title}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#each posts as post}
|
||||
<a href='{post.URL}' title='{post.title}' class='post' target='_blank'>
|
||||
<div class='media'>
|
||||
<img src='{post.image}' alt={post.title}/>
|
||||
</div>
|
||||
<div class='content'>
|
||||
<div class='subtitle'>{post.source.name}</div>
|
||||
<div class='title'>{post.title}</div>
|
||||
<div class='subtitle'>{when(post.date)}</div>
|
||||
{#if summary === 'true'}
|
||||
<div class='summary'>{@html post.summary}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<style>
|
||||
|
||||
#displayFeed {
|
||||
font-family:'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
|
||||
overflow-x: hidden;
|
||||
font-size: 1.2rem;
|
||||
width: 100%;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
#headerTitle {
|
||||
line-height: 45px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.post {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
transition: background .3s ease-in-out;
|
||||
border-radius: 5px;;
|
||||
}
|
||||
|
||||
.post:hover {
|
||||
background-color: #ececec;
|
||||
}
|
||||
|
||||
.post .media,
|
||||
.post .content {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-basis: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.post .content {
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
.post .content .title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.post .content .subtitle {
|
||||
font-weight: 100;
|
||||
color: #666;
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.post .content .summary {
|
||||
margin-top: 1rem;
|
||||
color: #333;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.post .content .summary a {
|
||||
color: orangered;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.post .media img {
|
||||
border-radius: 5px;
|
||||
flex: 1;
|
||||
max-height: 250px;
|
||||
width: 400px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
1
webcomponents/src/main.js
Normal file
1
webcomponents/src/main.js
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './DisplayFeed.svelte'
|
11
webcomponents/vite.config.js
Normal file
11
webcomponents/vite.config.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
svelte({
|
||||
compilerOptions: { customElement: true }
|
||||
})
|
||||
]
|
||||
})
|
13
webcomponents/vite.lib.config.js
Normal file
13
webcomponents/vite.lib.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: './src/main.js',
|
||||
name: 'GancioEvents'
|
||||
}
|
||||
},
|
||||
plugins: [svelte({compilerOptions: { customElement: true }})]
|
||||
})
|
329
webcomponents/yarn.lock
Normal file
329
webcomponents/yarn.lock
Normal file
|
@ -0,0 +1,329 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@rollup/pluginutils@^4.1.1":
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.1.tgz#1d4da86dd4eded15656a57d933fda2b9a08d47ec"
|
||||
integrity sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ==
|
||||
dependencies:
|
||||
estree-walker "^2.0.1"
|
||||
picomatch "^2.2.2"
|
||||
|
||||
"@sveltejs/vite-plugin-svelte@^1.0.0-next.11":
|
||||
version "1.0.0-next.31"
|
||||
resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.31.tgz#5d0d5445ed85a1af613224eacff78c69f14c7fad"
|
||||
integrity sha512-8K3DcGP1V+XBv389u32S6wt8xiun6hHd5wn28AKLSoNTIhOmJOA2RJUJzp0seTRI86Shme4lzHI2Fgq4qz1wXQ==
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^4.1.1"
|
||||
debug "^4.3.3"
|
||||
kleur "^4.1.4"
|
||||
magic-string "^0.25.7"
|
||||
require-relative "^0.8.7"
|
||||
svelte-hmr "^0.14.7"
|
||||
|
||||
csstype@^3.0.5:
|
||||
version "3.0.10"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5"
|
||||
integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==
|
||||
|
||||
debug@^4.3.3:
|
||||
version "4.3.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
dom-serializer@^1.0.1:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91"
|
||||
integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==
|
||||
dependencies:
|
||||
domelementtype "^2.0.1"
|
||||
domhandler "^4.2.0"
|
||||
entities "^2.0.0"
|
||||
|
||||
domelementtype@^2.0.1, domelementtype@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
|
||||
integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
|
||||
|
||||
domhandler@^4.0.0, domhandler@^4.2.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626"
|
||||
integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==
|
||||
dependencies:
|
||||
domelementtype "^2.2.0"
|
||||
|
||||
domutils@^2.5.2:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
|
||||
integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
|
||||
dependencies:
|
||||
dom-serializer "^1.0.1"
|
||||
domelementtype "^2.2.0"
|
||||
domhandler "^4.2.0"
|
||||
|
||||
entities@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
|
||||
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
|
||||
|
||||
esbuild-android-arm64@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.15.tgz#3fc3ff0bab76fe35dd237476b5d2b32bb20a3d44"
|
||||
integrity sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==
|
||||
|
||||
esbuild-darwin-64@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.15.tgz#8e9169c16baf444eacec60d09b24d11b255a8e72"
|
||||
integrity sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==
|
||||
|
||||
esbuild-darwin-arm64@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.15.tgz#1b07f893b632114f805e188ddfca41b2b778229a"
|
||||
integrity sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==
|
||||
|
||||
esbuild-freebsd-64@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.15.tgz#0b8b7eca1690c8ec94c75680c38c07269c1f4a85"
|
||||
integrity sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==
|
||||
|
||||
esbuild-freebsd-arm64@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.15.tgz#2e1a6c696bfdcd20a99578b76350b41db1934e52"
|
||||
integrity sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==
|
||||
|
||||
esbuild-linux-32@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.15.tgz#6fd39f36fc66dd45b6b5f515728c7bbebc342a69"
|
||||
integrity sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==
|
||||
|
||||
esbuild-linux-64@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.15.tgz#9cb8e4bcd7574e67946e4ee5f1f1e12386bb6dd3"
|
||||
integrity sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==
|
||||
|
||||
esbuild-linux-arm64@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.15.tgz#3891aa3704ec579a1b92d2a586122e5b6a2bfba1"
|
||||
integrity sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==
|
||||
|
||||
esbuild-linux-arm@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.15.tgz#8a00e99e6a0c6c9a6b7f334841364d8a2b4aecfe"
|
||||
integrity sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==
|
||||
|
||||
esbuild-linux-mips64le@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.15.tgz#36b07cc47c3d21e48db3bb1f4d9ef8f46aead4f7"
|
||||
integrity sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==
|
||||
|
||||
esbuild-linux-ppc64le@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.15.tgz#f7e6bba40b9a11eb9dcae5b01550ea04670edad2"
|
||||
integrity sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==
|
||||
|
||||
esbuild-netbsd-64@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.15.tgz#a2fedc549c2b629d580a732d840712b08d440038"
|
||||
integrity sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==
|
||||
|
||||
esbuild-openbsd-64@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.15.tgz#b22c0e5806d3a1fbf0325872037f885306b05cd7"
|
||||
integrity sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==
|
||||
|
||||
esbuild-sunos-64@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.15.tgz#d0b6454a88375ee8d3964daeff55c85c91c7cef4"
|
||||
integrity sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==
|
||||
|
||||
esbuild-windows-32@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.15.tgz#c96d0b9bbb52f3303322582ef8e4847c5ad375a7"
|
||||
integrity sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==
|
||||
|
||||
esbuild-windows-64@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.15.tgz#1f79cb9b1e1bb02fb25cd414cb90d4ea2892c294"
|
||||
integrity sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==
|
||||
|
||||
esbuild-windows-arm64@0.13.15:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.15.tgz#482173070810df22a752c686509c370c3be3b3c3"
|
||||
integrity sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==
|
||||
|
||||
esbuild@^0.13.2:
|
||||
version "0.13.15"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.15.tgz#db56a88166ee373f87dbb2d8798ff449e0450cdf"
|
||||
integrity sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==
|
||||
optionalDependencies:
|
||||
esbuild-android-arm64 "0.13.15"
|
||||
esbuild-darwin-64 "0.13.15"
|
||||
esbuild-darwin-arm64 "0.13.15"
|
||||
esbuild-freebsd-64 "0.13.15"
|
||||
esbuild-freebsd-arm64 "0.13.15"
|
||||
esbuild-linux-32 "0.13.15"
|
||||
esbuild-linux-64 "0.13.15"
|
||||
esbuild-linux-arm "0.13.15"
|
||||
esbuild-linux-arm64 "0.13.15"
|
||||
esbuild-linux-mips64le "0.13.15"
|
||||
esbuild-linux-ppc64le "0.13.15"
|
||||
esbuild-netbsd-64 "0.13.15"
|
||||
esbuild-openbsd-64 "0.13.15"
|
||||
esbuild-sunos-64 "0.13.15"
|
||||
esbuild-windows-32 "0.13.15"
|
||||
esbuild-windows-64 "0.13.15"
|
||||
esbuild-windows-arm64 "0.13.15"
|
||||
|
||||
estree-walker@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
||||
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||
|
||||
fsevents@~2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||
|
||||
function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||
|
||||
has@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
||||
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
htmlparser2@^6.0.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
|
||||
integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
|
||||
dependencies:
|
||||
domelementtype "^2.0.1"
|
||||
domhandler "^4.0.0"
|
||||
domutils "^2.5.2"
|
||||
entities "^2.0.0"
|
||||
|
||||
is-core-module@^2.2.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548"
|
||||
integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
kleur@^4.1.4:
|
||||
version "4.1.4"
|
||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.4.tgz#8c202987d7e577766d039a8cd461934c01cda04d"
|
||||
integrity sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==
|
||||
|
||||
magic-string@^0.25.7:
|
||||
version "0.25.7"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
|
||||
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
|
||||
dependencies:
|
||||
sourcemap-codec "^1.4.4"
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
nanoid@^3.1.30:
|
||||
version "3.1.30"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362"
|
||||
integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==
|
||||
|
||||
path-parse@^1.0.6:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
picocolors@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||
|
||||
picomatch@^2.2.2:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
|
||||
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
|
||||
|
||||
postcss@^8.3.8:
|
||||
version "8.4.4"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.4.tgz#d53d4ec6a75fd62557a66bb41978bf47ff0c2869"
|
||||
integrity sha512-joU6fBsN6EIer28Lj6GDFoC/5yOZzLCfn0zHAn/MYXI7aPt4m4hK5KC5ovEZXy+lnCjmYIbQWngvju2ddyEr8Q==
|
||||
dependencies:
|
||||
nanoid "^3.1.30"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.1"
|
||||
|
||||
require-relative@^0.8.7:
|
||||
version "0.8.7"
|
||||
resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de"
|
||||
integrity sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=
|
||||
|
||||
resolve@^1.20.0:
|
||||
version "1.20.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
|
||||
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
|
||||
dependencies:
|
||||
is-core-module "^2.2.0"
|
||||
path-parse "^1.0.6"
|
||||
|
||||
rollup@^2.57.0:
|
||||
version "2.60.2"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.60.2.tgz#3f45ace36a9b10b4297181831ea0719922513463"
|
||||
integrity sha512-1Bgjpq61sPjgoZzuiDSGvbI1tD91giZABgjCQBKM5aYLnzjq52GoDuWVwT/cm/MCxCMPU8gqQvkj8doQ5C8Oqw==
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
source-map-js@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
|
||||
integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==
|
||||
|
||||
sourcemap-codec@^1.4.4:
|
||||
version "1.4.8"
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
||||
|
||||
style-vendorizer@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/style-vendorizer/-/style-vendorizer-2.1.1.tgz#5f06601c1724cfb314fe1153e7e442c58dde771c"
|
||||
integrity sha512-gVO6Cwxtg8iX0X1W4xMhSc5WbQpiIBQDkhq3JkwebMRRgyhCfuvMrnPlTAGTRjfQPGRmzgjCOZ4drehTnLahHA==
|
||||
|
||||
svelte-hmr@^0.14.7:
|
||||
version "0.14.7"
|
||||
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.14.7.tgz#7fa8261c7b225d9409f0a86f3b9ea5c3ca6f6607"
|
||||
integrity sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog==
|
||||
|
||||
svelte@^3.37.0:
|
||||
version "3.44.2"
|
||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.44.2.tgz#3e69be2598308dfc8354ba584cec54e648a50f7f"
|
||||
integrity sha512-jrZhZtmH3ZMweXg1Q15onb8QlWD+a5T5Oca4C1jYvSURp2oD35h4A5TV6t6MEa93K4LlX6BkafZPdQoFjw/ylA==
|
||||
|
||||
twind@^0.16.16:
|
||||
version "0.16.16"
|
||||
resolved "https://registry.yarnpkg.com/twind/-/twind-0.16.16.tgz#76959cd21897528f9a2631a293e3381b668332ad"
|
||||
integrity sha512-UlAYjkGCdgjg4xU1SIwRW0PpG0anZrY32+kS2jYsi32yb4RuW0ttzaI1OglgwAUk/rZwzoINilnIFORzOSFZag==
|
||||
dependencies:
|
||||
csstype "^3.0.5"
|
||||
htmlparser2 "^6.0.0"
|
||||
style-vendorizer "^2.0.0"
|
||||
|
||||
vite@^2.6.4:
|
||||
version "2.6.14"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-2.6.14.tgz#35c09a15e4df823410819a2a239ab11efb186271"
|
||||
integrity sha512-2HA9xGyi+EhY2MXo0+A2dRsqsAG3eFNEVIo12olkWhOmc8LfiM+eMdrXf+Ruje9gdXgvSqjLI9freec1RUM5EA==
|
||||
dependencies:
|
||||
esbuild "^0.13.2"
|
||||
postcss "^8.3.8"
|
||||
resolve "^1.20.0"
|
||||
rollup "^2.57.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
Loading…
Reference in a new issue