real-time events in store, quoting parts of messages

This commit is contained in:
كارل مبارك 2021-07-02 14:35:21 +02:00
parent 634613555a
commit 6d31104419
9 changed files with 211 additions and 204 deletions

View file

@ -1,3 +1,3 @@
VUE_APP_ZULIP_email=pub-bot@chat.hackersanddesigners.nl
VUE_APP_ZULIP_key=PhvWW1FG1SV4UCZ10z61Evd6Nu1o4qRc
VUE_APP_ZULIP_key=m1MDxscGcPQx2RvIfgG4DiSHE1nurxms
VUE_APP_ZULIP_site=https://chat.hackersanddesigners.nl

View file

@ -1,5 +1,10 @@
<template>
<div id="app" :class="{ mobile: isMobile }">
<div
id="app"
:class="[
stream,
{ mobile: isMobile }
]">
<Styles />
<!-- <header>
@ -29,10 +34,11 @@ export default {
return {
api: api,
zulipClient: null,
stream: null,
};
},
computed: {
...mapState(["isMobile", "pubStr"]),
...mapState(["isMobile", "pubStr", "currentStream"]),
},
created() {
this.$store.commit("setMobile", this.checkIfMobile());
@ -43,9 +49,10 @@ export default {
this.getStreams();
this.$router.afterEach((to) => {
const stream = to.path.replace("/", "");
if (stream != "") {
this.setUpDoc(stream);
this.$store.commit("setCurStream", to.path.replace("/", ""))
this.stream = to.path.replace("/", "")
if (this.stream != "") {
this.setUpDoc(this.stream);
} else {
this.$store.commit("setContents", []);
this.$store.commit("setRules", []);
@ -85,12 +92,16 @@ export default {
.zulip
.getMsgs(this.zulipClient, stream, 'content')
.then(result => {
this
.$store
.commit( 'setContents',
result
.messages
)
for (let m = 0; m < result.messages.length; m++) {
const message = result.messages[m]
this.$store.commit('addMessage', message)
}
// this
// .$store
// .commit( 'setContents',
// result
// .messages
// )
})
api
@ -102,112 +113,56 @@ export default {
.commit( 'setRules',
result
.messages
.map(m =>
this.toCSS(m)
)
)
})
api.zulip.listen(this.zulipClient)
api.zulip.listen(this.zulipClient, this.eventHandler)
},
toCSS(poll) {
eventHandler(event) {
console.log(event)
switch (event.type) {
const
subs = poll
.submessages
.map(s => JSON.parse(s.content))
let
className = '',
emoji_code = '',
options = [],
rules = []
subs.forEach(sub => {
// console.log(sub)
if (
sub.widget_type &&
sub.widget_type == 'poll'
) {
className = sub.extra_data.question
options = sub.extra_data.options
emoji_code = this.toEmojiCode(className)
// console.log(emoji_code)
if (options) {
options.forEach(option =>
rules
.push(
this.constructRule(option, options, subs)
)
)
case 'message':
switch (event.message.subject) {
case 'content':
this.$store.commit('addMessage', event.message)
break
case 'rules':
this.$store.commit('addRule', event.message)
break
}
} else if (
sub.type &&
sub.type == 'new_option'
) {
rules
.push(
this.constructRule(sub.option, options, subs)
)
break
case 'delete_message':
this.$store.commit('deleteMessage', event.message_id)
break
case 'update_message':
this.$store.commit('editMessage', {
mid: event.message_id,
content: event.content
})
break
case 'reaction':
this.$store.commit(`${event.op}Reaction`, {
mid: event.message_id,
reaction: {
emoji_code: event.emoji_code,
emoji_name: event.emoji_name,
reaction_type: event.reaction_type,
}
})
break
return {
className,
emoji_code,
rules
default:
console.log("Event type unknown", event.type)
}
}
},
constructRule(option, options, subs) {
const
text = option,
votes = subs.filter(s => (
s.type == 'vote' &&
s.key.replace('canned,', '') == options.indexOf(option)
)),
weight =
votes.length > 0
?
votes
.map(s => s.vote)
.reduce((a,b) => a + b)
:
0
return {
text,
weight
}
},
toEmojiCode: (emoji) =>
emoji.replace(/\p{Emoji}/gu, (m) => m.codePointAt(0).toString(16)),
// minimal validation. rules have to contain a colon and semicolon
validateRule: (rule) => {
return rule.text.match(/.+:.+;/gm);
},
api.zulip.getMsgs(this.zulipClient, stream, "content").then((result) => {
this.$store.commit("setContents", result.messages);
});
api.zulip.getMsgs(this.zulipClient, stream, "rules").then((result) => {
console.log("messages!",result)
this.$store.commit(
"setRules",
result
// result.messages
// .filter((m) => m.content.match(/\/poll/gm))
// .map((m) => this.toCSS(m))
);
});
api.zulip.listen(this.zulipClient);
},
},
};
</script>

View file

@ -33,7 +33,7 @@ const
anchor: "newest",
num_before: 100,
num_after: 0,
// apply_markdown: false,
apply_markdown: false,
narrow: [
{ operator: "stream", operand: stream },
{ operator: "topic", operand: topic },
@ -44,10 +44,10 @@ const
})
),
listen = client => {
listen = (client, cb) => {
client
.callOnEachEvent(
event => console.log('Got Event:', event),
event => cb(event),
[ 'message' ],
[ { operator: "stream", operand: "chatty" } ]
)

View file

@ -1,10 +1,7 @@
<template>
<span :class="classes" :x-style="styles">
<!-- {{ $md.renderInline(content) }} -->
<span :class="classes">
<vue3-markdown-it :source="content" v-bind="$mdOpts"></vue3-markdown-it>
</span>
<!-- <div v-html="content"/> -->
<!-- {{ content }} -->
</template>
<script>
@ -16,25 +13,35 @@ export default {
return "```json\n" + JSON.stringify(this.message, null, 2) + "\n```";
},
content() {
let url = process.env.VUE_APP_ZULIP_site;
let m = this.message.content.replace("\n", "<br/>");
m = m.replaceAll('src="','src="' + url);
m = m.replaceAll('href="/','href="' + url + "/");
return m
let c = this.message.content.replace("\n", "<br/>");
c = c.replaceAll('src="','src="' + url);
c = c.replaceAll('href="/','href="' + url + "/");
const referrers = this.$store.state.contents.filter(m => (
m.responseTo &&
m.responseTo.id == this.message.id &&
m.responseTo.sender_id == this.message.sender_id &&
this.message.content.includes(m.responseTo.quote)
))
referrers.forEach(m => {
const classes = m.reactions
.map(r => "u" + r.emoji_code)
.join(' ')
c = c.replace(
m.responseTo.quote,
`<span class="${classes}">${m.responseTo.quote}</span>`
)
})
return c
},
classes() {
return this.message.reactions.map((r) => "u" + r.emoji_code);
},
styles() {
return this.$store.state.rules
.filter((r) => this.classes.includes("u" + r.emoji_code))
.map((r) => r.rules)
.flat()
.map((s) => s.text);
},
},
created() {
console.log(this.message.content);
// console.log(this.message.content);
},
};
</script>

View file

@ -1,24 +1,6 @@
<template>
<section class="content">
<p class="title">{{ $.type.name }}</p>
<!-- <vue-html2pdf
:show-layout="false"
:float-layout="true"
:enable-download="true"
:preview-modal="true"
:paginate-elements-by-height="1400"
filename="hee hee"
:pdf-quality="2"
:manual-pagination="false"
pdf-format="a5"
pdf-orientation="portrait"
pdf-content-width="800px"
@progress="onProgress($event)"
@hasStartedGeneration="hasStartedGeneration()"
@hasGenerated="hasGenerated($event)"
ref="body"
> -->
<div class="body">
<span
v-for="message in contents"
@ -30,20 +12,17 @@
<span>&nbsp;</span>
</span>
</div>
<!-- </vue-html2pdf> -->
</section>
</template>
<script>
import { mapState } from 'vuex'
// import VueHtml2pdf from 'vue-html2pdf'
import Message from './Message'
export default {
name: 'Content',
components: {
Message,
// VueHtml2psdf
},
computed: {
...mapState([
@ -52,9 +31,6 @@ export default {
])
},
methods: {
toPDF() {
this.$refs.body.generatePdf()
}
}
}
</script>

View file

@ -5,7 +5,10 @@
inline
></vue3-markdown-it>
</span> -->
<code class="rule">
<code
class="rule"
:class="rule.emoji_code"
>
<p>{{ rule.className }} {</p>
<p
v-for="dec in rule.rules"
@ -29,7 +32,11 @@ export default {
}
</script>
<style>
<style scoped>
.rule {
margin: 1em 0;
}
.rule p {
margin: 0;

View file

@ -9,15 +9,26 @@ export default {
data: function(){
return {
el: null,
htmlTags: [
'section',
'p',
'a',
'span',
'code',
'ul',
'li',
'and so on'
]
}
},
methods: {
generateStyleRules() {
let styles = "";
this.rules.map((r)=>{
styles += r.className
this.rules.map(r => {
const dot = this.htmlTags.indexOf(r.className) > -1 ? '' : '.'
styles += `.${r.parentClassName} ${dot}${r.className}`
if( this.containsEmoji(r.className)){
styles += ", .u" + this.toEmojiCode(r.className)
styles += `, .${r.parentClassName} .u${this.toEmojiCode(r.className)}`
}
styles += "{"
r.rules.map((s)=>{
@ -25,6 +36,7 @@ export default {
})
styles += "}"
})
console.log(styles)
return styles;
},
insertStyleElement() {

View file

@ -1,6 +1,5 @@
<template>
<div>
<!-- {{ $md.renderInline(rawJSON) }} -->
<p class="name">
<router-link :to="stream.name">
{{ stream.name }}

View file

@ -1,54 +1,55 @@
import { createStore } from 'vuex'
import emoji from "../mixins/emoji";
let toCSS = (poll) => {
let className = "",
const toCSS = (poll, currentStream) => {
let
className = "",
emoji_code = "",
options = [],
rules = [],
subs = poll.submessages.map((s) => JSON.parse(s.content));
subs.forEach((sub) => {
subs = poll.submessages.map((s) => JSON.parse(s.content)),
parentClassName = currentStream
subs.forEach(sub => {
if (sub.widget_type && sub.widget_type == "poll") {
className = sub.extra_data.question;
emoji_code = emoji.methods.toEmojiCode(className);
// console.log(emoji_code);
options = sub.extra_data.options;
className = sub.extra_data.question
emoji_code = emoji.methods.toEmojiCode(className)
options = sub.extra_data.options
// console.log(emoji_code)
if (options) {
options.forEach((option) => {
let r = constructRule(option, options, subs);
options.forEach(option => {
let r = constructRule(option, options, subs)
if (validateRule(r)) {
rules.push(r);
rules.push(r)
}
});
})
}
} else if (sub.type && sub.type == "new_option") {
let r = constructRule(sub.option, options, subs);
let r = constructRule(sub.option, options, subs)
if (validateRule(r)) {
rules.push(r);
rules.push(r)
}
}
});
return { className, emoji_code, rules };
})
return { parentClassName, className, emoji_code, rules }
}
let constructRule = (option, options, subs) => {
const text = option,
votes = subs.filter(
(s) =>
const constructRule = (option, options, subs) => {
const
text = option,
votes = subs.filter( s =>
s.type == "vote" &&
s.key.replace("canned,", "") == options.indexOf(option)
),
weight =
votes.length > 0
? votes.map((s) => s.vote).reduce((a, b) => a + b)
: 0;
return { text, weight };
: 0
return { text, weight }
}
// minimal validation. rules have to contain a colon and semicolon
let validateRule = (rule) => {
return rule.text.match(/.+:.+;/gm);
}
const validateRule = rule => rule.text.match(/.+:.+;/gm)
export default createStore({
@ -57,7 +58,8 @@ export default createStore({
state: {
isMobile : false,
streams : [],
contents : {},
currentStream: '',
contents : [],
rules : [],
pubStr : 'pub-',
},
@ -65,26 +67,75 @@ export default createStore({
mutations: {
setMobile : (state, mobile) => state.isMobile = mobile,
setStreams : (state, streams) => state.streams = streams,
setContents : (state, messages) => state.contents = messages,
setRules: (state, messages) => {
state.rules = messages.messages
.filter((m) => m.content.match(/\/poll/gm))
.map((m) => toCSS(m))
setCurStream : (state, stream) => state.currentStream = stream,
addMessage: (state, message) => {
if (message.content.startsWith('@_**')) {
message.responseTo = {
id: message.content
.replace(/.*\/near\//gm, '')
.replace(/\):.*[^]+/gm, ''),
sender_id: message.content
.replace(/@_\*\*.*\|/gm, '')
.replace(/\*\*.\[said\].*[^]+/gm, ''),
quote: message.content
.replace(/[^]+.*```quote\n/gm, '')
.replace(/ \n```/gm, '')
}
// console.log(message.responseTo)
// const referenceMessage = state.contents.find(m => {
// m.id == message.responseTo.id
// // &&
// // m.sender_id == message.responseTo.sender_id
// // m.content.includes(message.responseTo.quote)
// })
// console.log(referenceMessage)
}
state.contents.push(message)
},
selectTag : (state, tag) => state.selectedTag = tag,
editMessage: (state, { mid, content }) => {
const message = state.contents.find(m => m.id == mid)
if (message) {
message.content = content
}
},
deleteMessage: (state, mid) => {
const message = state.contents.find(m => m.id == mid)
if (message) {
state.contents.splice(state.contents.indexOf(message), 1)
}
},
addReaction : (state, { mid, reaction }) => {
const message = state.contents.find(m => m.id == mid)
if (message) {
message.reactions.push(reaction)
}
},
removeReaction : (state, { mid, reaction }) => {
const message = state.contents.find(m => m.id == mid)
if (message) {
message.reactions.splice(message.reactions.indexOf(reaction), 1)
}
},
setRules: (state, rules) => { state.rules =
rules
.filter((r) => r.content.match(/\/poll/gm))
.map((r) => toCSS(r, state.currentStream))
},
addRule: (state, rule) => {
if (rule.content.match(/\/poll/gm)) {
state.rules.push(toCSS(rule))
}
}
},
actions: {
},
getters: {
filteredResources: state => ( state
.resources
.filter(r => (
r.tags.indexOf(state.selectedTag > -1)
))
)
}
})