real-time events in store, quoting parts of messages
This commit is contained in:
parent
634613555a
commit
6d31104419
9 changed files with 211 additions and 204 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,111 +113,55 @@ 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) {
|
||||
|
||||
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)
|
||||
)
|
||||
)
|
||||
eventHandler(event) {
|
||||
console.log(event)
|
||||
switch (event.type) {
|
||||
|
||||
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)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
className,
|
||||
emoji_code,
|
||||
rules
|
||||
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
|
||||
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -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" } ]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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> </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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- {{ $md.renderInline(rawJSON) }} -->
|
||||
<p class="name">
|
||||
<router-link :to="stream.name">
|
||||
{{ stream.name }}
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
options = [],
|
||||
rules = [],
|
||||
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) =>
|
||||
s.type == "vote" &&
|
||||
s.key.replace("canned,", "") == options.indexOf(option)
|
||||
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 };
|
||||
? votes.map((s) => s.vote).reduce((a, b) => a + b)
|
||||
: 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({
|
||||
|
||||
|
|
@ -56,35 +57,85 @@ export default createStore({
|
|||
|
||||
state: {
|
||||
isMobile : false,
|
||||
streams : [],
|
||||
contents : {},
|
||||
rules : [],
|
||||
pubStr: 'pub-',
|
||||
streams : [],
|
||||
currentStream: '',
|
||||
contents : [],
|
||||
rules : [],
|
||||
pubStr : 'pub-',
|
||||
},
|
||||
|
||||
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)
|
||||
))
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue