Files
2026-09-15 21:27:46 +05:00

336 lines
11 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<title>Watermill Server-Sent Events Example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
</head>
<body>
<style>
body {
padding-top: 5rem;
}
.example {
padding: 3rem 1.5rem;
}
#feeds li {
display: inline;
}
.breadcrumb-item + .breadcrumb-item::before {
content: "|";
}
</style>
<div id="app">
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<a class="navbar-brand" href="/">Home</a>
</nav>
<main role="main" class="container">
<div class="example">
<feeds-list></feeds-list>
<add-post></add-post>
<hr />
<router-view></router-view>
</div>
</main>
</div>
</body>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="https://unpkg.com/vue@2.7.16/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router@3.6.5/dist/vue-router.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-resource@1.3.5"></script>
<script type="text/x-template" id="feeds-list">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item" v-bind:class="{ active: feed.active }" v-for="feed in feeds">
<router-link v-if="!feed.active" :to="{ name: 'show-feed', params: { feed_name: feed.name }}">
{{ feed.text }}
</router-link>
<span v-else>
{{ feed.text }}
</span>
</li>
</ol>
</nav>
</script>
<script type="text/x-template" id="add-post">
<div>
<p>
<button class="btn btn-primary" type="button" v-on:click="toggle">
Add new post
</button>
<button class="btn btn-info" type="button" v-on:click="generatePost">Add randomly generated post</button>
</p>
<div id="addPostForm" v-show="showForm">
<div class="card card-body">
<h2>Add new post</h2>
<form v-on:submit.prevent="submitNewPost">
<div class="form-group row">
<label for="inputTitle" class="col-sm-2 col-form-label">Title</label>
<div class="col-sm-10">
<input type="title" class="form-control" id="inputTitle" placeholder="Title" v-model="post.title">
</div>
</div>
<div class="form-group row">
<label for="inputAuthor" class="col-sm-2 col-form-label">Author</label>
<div class="col-sm-10">
<input type="author" class="form-control" id="inputAuthor" placeholder="Author" v-model="post.author">
</div>
</div>
<div class="form-group row">
<label for="inputContent" class="col-sm-2 col-form-label">Content</label>
<div class="col-sm-10">
<textarea class="form-control" id="inputContent" rows="3" v-model="post.content"></textarea>
</div>
</div>
<div class="form-group row">
<div class="col-sm-2"></div>
<div class="col-sm-10">
<input type="submit" class="btn btn-success" value="Add post" />
</div>
</div>
</form>
</div>
</div>
</div>
</script>
<script type="text/x-template" id="home">
<div>
<h3>1. Add some posts using the buttons above (use #hashtags).</h3>
<h3>2. Click one of feeds to see a live stream.</h3>
</div>
</script>
<script type="text/x-template" id="feed-template">
<div>
<h2>{{ feed }}</h2>
<div v-for="post in posts">
<div class="card">
<div class="card-body">
<h4 class="card-title">
{{post.title}}
<router-link :to="{ name: 'edit-post', params: { post_id: post.id }}"
class="btn btn-light class-right btn-sm">Edit
</router-link>
</h4>
<p class="card-text" v-html="post.content"></p>
<p class="card-text">
<small class="text-muted">Author: {{post.author}}</small>
</p>
</div>
</div>
<br>
</div>
</div>
</script>
<script type="text/x-template" id="post-edit-template">
<div>
<h2>Edit post</h2>
<div class="card">
<div class="card-body">
<form v-on:submit.prevent="submitData">
<div class="form-group row">
<label for="inputTitle" class="col-sm-2 col-form-label">Title</label>
<div class="col-sm-10">
<input type="title" class="form-control" id="inputTitle" placeholder="Title" v-model="post.title">
</div>
</div>
<div class="form-group row">
<label for="inputContent" class="col-sm-2 col-form-label">Content</label>
<div class="col-sm-10">
<textarea class="form-control" id="inputContent" rows="3" v-model="post.content"></textarea>
</div>
</div>
<div class="form-group row">
<div class="col-sm-2"></div>
<div class="col-sm-10">
<input type="submit" class="btn btn-success" value="Update post" />
</div>
</div>
</form>
</div>
</div>
</div>
</script>
<script>
Vue.component('feeds-list', {
template: '#feeds-list',
data: function () {
return {
feeds_stream: [],
current_feed: "",
}
},
created() {
this.es = new EventSource('/api/feeds')
this.es.addEventListener('data', event => {
let data = JSON.parse(event.data);
this.feeds_stream = data.feeds;
}, false);
},
watch: {
$route(to, from) {
if (to.name === "show-feed") {
this.current_feed = to.params.feed_name;
} else {
this.current_feed = "";
}
}
},
computed: {
feeds: function (){
return this.feeds_stream.map(f => ({text: `${f.name} (${f.posts})`, name: f.name, active: f.name === this.current_feed}));
},
},
});
const Home = {
template: '#home',
}
Vue.component('add-post', {
template: '#add-post',
data: function () {
return {
post: {},
showForm: false,
}
},
methods: {
submitNewPost: function() {
postData = this.post;
this.$http.post('/api/posts', JSON.stringify(postData))
this.post = {};
},
generatePost: function() {
this.$http.post('/api/generate/post')
},
toggle: function () {
this.showForm = !this.showForm;
},
},
created() {},
});
const ShowFeed = {
template: '#feed-template',
data: function () {
return {
feed: "",
posts_stream: [],
}
},
watch: {
$route(to, from) {
console.log(to)
this.setupFeed(to.params.feed_name);
}
},
created() {
this.setupFeed(this.$route.params.feed_name);
},
destroyed() {
if (this.es) {
this.es.close();
}
},
methods: {
setupFeed: function(feed_name) {
this.feed = feed_name;
if (this.es) {
this.es.close();
}
this.es = new EventSource('/api/feeds/' + this.feed)
this.es.addEventListener('data', event => {
let data = JSON.parse(event.data);
this.posts_stream = data.posts;
}, false);
},
},
computed: {
posts: function() {
var posts = this.posts_stream.slice();
for (var p of posts) {
p.content = p.content.replace(/#([\w]+)/g,'<a href="#/feed/$1">#$1</a>')
}
return posts;
},
},
};
const EditPost = {
template: "#post-edit-template",
data: function () {
return {
post_id: this.$route.params.post_id,
post: {},
}
},
created() {
this.setupPost();
},
destroyed() {
if (this.es) {
console.log("closing")
this.es.close();
}
},
methods: {
submitData: function () {
postData = this.post;
this.$http.patch('/api/posts/' + this.post_id, JSON.stringify(postData));
router.go(-1);
},
setupPost() {
this.es = new EventSource('/api/posts/' + this.post_id)
this.es.addEventListener('data', event => {
let data = JSON.parse(event.data);
this.post = data;
}, false);
this.es.addEventListener('error', function (e) {
if (e.readyState == EventSource.CLOSED) {
console.log("close", e)
} else {
console.log("other error", e)
}
}, false);
},
},
};
const routes = [
{path: '/', component: Home, name: 'home'},
{path: '/edit-post/:post_id', component: EditPost, name: 'edit-post'},
{path: '/feed/:feed_name', component: ShowFeed, name: 'show-feed'},
];
const router = new VueRouter({
routes
});
const app = new Vue({
router
}).$mount('#app')
</script>
</html>