Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bde3819510 | ||
|
|
097889ba3f | ||
|
|
4854d40508 | ||
|
|
7dd3133475 | ||
|
|
24dfa39f91 | ||
|
|
7ec65f7c12 | ||
|
|
4f8edef42b | ||
|
|
7818410d55 | ||
|
|
1762191b03 | ||
|
|
2df92e809e | ||
|
|
59a8d32e40 | ||
|
|
c3b3963760 | ||
|
|
a0cd39e3bc | ||
|
|
e48b2258e0 | ||
|
|
35aaf8824f |
@@ -13,7 +13,7 @@ class ChatService(BaseService):
|
|||||||
channel["last_message_on"] = now()
|
channel["last_message_on"] = now()
|
||||||
await self.services.channel.save(channel)
|
await self.services.channel.save(channel)
|
||||||
await self.services.socket.broadcast(
|
await self.services.socket.broadcast(
|
||||||
channel_uid,
|
channel['uid'],
|
||||||
{
|
{
|
||||||
"message": channel_message["message"],
|
"message": channel_message["message"],
|
||||||
"html": channel_message["html"],
|
"html": channel_message["html"],
|
||||||
|
|||||||
+1
-1
@@ -45,7 +45,7 @@ async def start_ssh_server(app,host,port):
|
|||||||
logger.info("Starting SFTP server setup")
|
logger.info("Starting SFTP server setup")
|
||||||
|
|
||||||
host_key_path = Path("drive") / ".ssh" / "sftp_server_key"
|
host_key_path = Path("drive") / ".ssh" / "sftp_server_key"
|
||||||
host_key_path.parent.mkdir(exist_ok=True)
|
host_key_path.parent.mkdir(exist_ok=True, parents=True)
|
||||||
try:
|
try:
|
||||||
if not host_key_path.exists():
|
if not host_key_path.exists():
|
||||||
logger.info(f"Generating new host key at {host_key_path}")
|
logger.info(f"Generating new host key at {host_key_path}")
|
||||||
|
|||||||
+11
-5
@@ -160,11 +160,15 @@ export class App extends EventHandler {
|
|||||||
typeLock = null;
|
typeLock = null;
|
||||||
typeListener = null;
|
typeListener = null;
|
||||||
typeEventChannelUid = null;
|
typeEventChannelUid = null;
|
||||||
async set_typing(channel_uid) {
|
_debug = false
|
||||||
|
async set_typing(channel_uid) {
|
||||||
this.typeEventChannel_uid = channel_uid;
|
this.typeEventChannel_uid = channel_uid;
|
||||||
}
|
}
|
||||||
|
debug() {
|
||||||
async ping(...args) {
|
this._debug = !this._debug;
|
||||||
|
this.ws._debug = this._debug;
|
||||||
|
}
|
||||||
|
async ping(...args) {
|
||||||
if (this.is_pinging) return false;
|
if (this.is_pinging) return false;
|
||||||
this.is_pinging = true;
|
this.is_pinging = true;
|
||||||
await this.rpc.ping(...args);
|
await this.rpc.ping(...args);
|
||||||
@@ -202,8 +206,10 @@ export class App extends EventHandler {
|
|||||||
this.ws.addEventListener("channel-message", (data) => {
|
this.ws.addEventListener("channel-message", (data) => {
|
||||||
me.emit("channel-message", data);
|
me.emit("channel-message", data);
|
||||||
});
|
});
|
||||||
this.ws.addEventListener("event", (data) => {
|
this.ws.addEventListener("data", (data) => {
|
||||||
console.info("aaaa");
|
if(this._debug){
|
||||||
|
console.debug(data)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.rpc.getUser(null).then((user) => {
|
this.rpc.getUser(null).then((user) => {
|
||||||
me.user = user;
|
me.user = user;
|
||||||
|
|||||||
@@ -544,3 +544,44 @@ dialog .dialog-button.secondary:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.embed-url-link {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embed-url-link img,
|
||||||
|
.embed-url-link video,
|
||||||
|
.embed-url-link iframe,
|
||||||
|
.embed-url-link div {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 400px;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embed-url-link .page-site {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #aaa;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embed-url-link .page-name {
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: #f05a28;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embed-url-link .page-description {
|
||||||
|
font-size: 1em;
|
||||||
|
color: #e6e6e6;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embed-url-link .page-link {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #f05a28;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
@@ -4,13 +4,23 @@ class ChatInputComponent extends HTMLElement {
|
|||||||
autoCompletions = {
|
autoCompletions = {
|
||||||
"example 1": () => {},
|
"example 1": () => {},
|
||||||
"example 2": () => {},
|
"example 2": () => {},
|
||||||
};
|
}
|
||||||
|
hiddenCompletions = {
|
||||||
|
"/starsRender": () => {
|
||||||
|
app.rpc.starsRender(this.channelUid,this.value.replace("/starsRender ",""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
users = []
|
||||||
|
textarea = null
|
||||||
|
_value = ""
|
||||||
|
lastUpdateEvent = null
|
||||||
|
previousValue = ""
|
||||||
|
lastChange = null
|
||||||
|
changed = false
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.lastUpdateEvent = new Date();
|
this.lastUpdateEvent = new Date();
|
||||||
this.textarea = document.createElement("textarea");
|
this.textarea = document.createElement("textarea");
|
||||||
this._value = "";
|
|
||||||
this.value = this.getAttribute("value") || "";
|
this.value = this.getAttribute("value") || "";
|
||||||
this.previousValue = this.value;
|
this.previousValue = this.value;
|
||||||
this.lastChange = new Date();
|
this.lastChange = new Date();
|
||||||
@@ -25,12 +35,15 @@ class ChatInputComponent extends HTMLElement {
|
|||||||
this._value = value || "";
|
this._value = value || "";
|
||||||
this.textarea.value = this._value;
|
this.textarea.value = this._value;
|
||||||
}
|
}
|
||||||
|
get allAutoCompletions() {
|
||||||
|
return Object.assign({},this.autoCompletions,this.hiddenCompletions)
|
||||||
|
}
|
||||||
resolveAutoComplete() {
|
resolveAutoComplete() {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
let value = null;
|
let value = null;
|
||||||
Object.keys(this.autoCompletions).forEach((key) => {
|
|
||||||
if (key.startsWith(this.value)) {
|
Object.keys(this.allAutoCompletions).forEach((key) => {
|
||||||
|
if (key.startsWith(this.value.split(" ")[0])) {
|
||||||
count++;
|
count++;
|
||||||
value = key;
|
value = key;
|
||||||
}
|
}
|
||||||
@@ -149,7 +162,7 @@ levenshteinDistance(a, b) {
|
|||||||
const me = this;
|
const me = this;
|
||||||
this.liveType = this.getAttribute("live-type") === "true";
|
this.liveType = this.getAttribute("live-type") === "true";
|
||||||
this.liveTypeInterval =
|
this.liveTypeInterval =
|
||||||
parseInt(this.getAttribute("live-type-interval")) || 3;
|
parseInt(this.getAttribute("live-type-interval")) || 6;
|
||||||
this.channelUid = this.getAttribute("channel");
|
this.channelUid = this.getAttribute("channel");
|
||||||
|
|
||||||
app.rpc.getRecentUsers(this.channelUid).then(users=>{
|
app.rpc.getRecentUsers(this.channelUid).then(users=>{
|
||||||
@@ -179,9 +192,6 @@ levenshteinDistance(a, b) {
|
|||||||
if (e.key === "Enter" && !e.shiftKey) {
|
if (e.key === "Enter" && !e.shiftKey) {
|
||||||
this.value = "";
|
this.value = "";
|
||||||
e.target.value = "";
|
e.target.value = "";
|
||||||
if(this.messageUid){
|
|
||||||
app.rpc.finalizeMessage(this.messageUid)
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.value = e.target.value;
|
this.value = e.target.value;
|
||||||
@@ -191,9 +201,10 @@ levenshteinDistance(a, b) {
|
|||||||
|
|
||||||
this.textarea.addEventListener("keydown", (e) => {
|
this.textarea.addEventListener("keydown", (e) => {
|
||||||
this.value = e.target.value;
|
this.value = e.target.value;
|
||||||
|
let autoCompletion = null;
|
||||||
if (e.key === "Tab") {
|
if (e.key === "Tab") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let autoCompletion = this.resolveAutoComplete();
|
autoCompletion = this.resolveAutoComplete();
|
||||||
if (autoCompletion) {
|
if (autoCompletion) {
|
||||||
e.target.value = autoCompletion;
|
e.target.value = autoCompletion;
|
||||||
this.value = autoCompletion;
|
this.value = autoCompletion;
|
||||||
@@ -209,18 +220,18 @@ levenshteinDistance(a, b) {
|
|||||||
if (!message) {
|
if (!message) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let autoCompletionHandler = this.allAutoCompletions[this.value.split(" ")[0]];
|
||||||
let autoCompletion = this.autoCompletions[message];
|
if (autoCompletionHandler) {
|
||||||
if (autoCompletion) {
|
autoCompletionHandler();
|
||||||
this.value = "";
|
this.value = "";
|
||||||
this.previousValue = "";
|
this.previousValue = "";
|
||||||
e.target.value = "";
|
e.target.value = "";
|
||||||
autoCompletion();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateMessage()
|
this.updateMessage()
|
||||||
|
app.rpc.finalizeMessage(this.messageUid)
|
||||||
this.value = "";
|
this.value = "";
|
||||||
this.previousValue = "";
|
this.previousValue = "";
|
||||||
this.messageUid = null;
|
this.messageUid = null;
|
||||||
|
|||||||
@@ -51,8 +51,6 @@ class MessageList extends HTMLElement {
|
|||||||
return this.isElementVisible(this.querySelector(".message-list-bottom"));
|
return this.isElementVisible(this.querySelector(".message-list-bottom"));
|
||||||
}
|
}
|
||||||
scrollToBottom(force) {
|
scrollToBottom(force) {
|
||||||
console.info("Scrolling down")
|
|
||||||
// if (force) {
|
|
||||||
this.scrollTop = this.scrollHeight;
|
this.scrollTop = this.scrollHeight;
|
||||||
|
|
||||||
this.querySelector(".message-list-bottom").scrollIntoView();
|
this.querySelector(".message-list-bottom").scrollIntoView();
|
||||||
@@ -61,7 +59,6 @@ class MessageList extends HTMLElement {
|
|||||||
this.scrollTop = this.scrollHeight;
|
this.scrollTop = this.scrollHeight;
|
||||||
this.querySelector(".message-list-bottom").scrollIntoView();
|
this.querySelector(".message-list-bottom").scrollIntoView();
|
||||||
},200)
|
},200)
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
updateMessageText(uid, message) {
|
updateMessageText(uid, message) {
|
||||||
const messageDiv = this.querySelector('div[data-uid="' + uid + '"]');
|
const messageDiv = this.querySelector('div[data-uid="' + uid + '"]');
|
||||||
@@ -69,12 +66,15 @@ class MessageList extends HTMLElement {
|
|||||||
if (!messageDiv) {
|
if (!messageDiv) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const scrollToBottom = this.isScrolledToBottom();
|
||||||
const receivedHtml = document.createElement("div");
|
const receivedHtml = document.createElement("div");
|
||||||
receivedHtml.innerHTML = message.html;
|
receivedHtml.innerHTML = message.html;
|
||||||
const html = receivedHtml.querySelector(".text").innerHTML;
|
const html = receivedHtml.querySelector(".text").innerHTML;
|
||||||
const textElement = messageDiv.querySelector(".text");
|
const textElement = messageDiv.querySelector(".text");
|
||||||
textElement.innerHTML = html;
|
textElement.innerHTML = html;
|
||||||
textElement.style.display = message.text == "" ? "none" : "block";
|
textElement.style.display = message.text == "" ? "none" : "block";
|
||||||
|
if(scrollToBottom)
|
||||||
|
this.scrollToBottom(true)
|
||||||
}
|
}
|
||||||
triggerGlow(uid,color) {
|
triggerGlow(uid,color) {
|
||||||
app.starField.glowColor(color)
|
app.starField.glowColor(color)
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export class Socket extends EventHandler {
|
|||||||
|
|
||||||
shouldReconnect = true;
|
shouldReconnect = true;
|
||||||
|
|
||||||
|
_debug = false;
|
||||||
|
|
||||||
get isConnected() {
|
get isConnected() {
|
||||||
return this.ws && this.ws.readyState === WebSocket.OPEN;
|
return this.ws && this.ws.readyState === WebSocket.OPEN;
|
||||||
}
|
}
|
||||||
@@ -112,7 +114,12 @@ export class Socket extends EventHandler {
|
|||||||
get(_, prop) {
|
get(_, prop) {
|
||||||
return (...args) => {
|
return (...args) => {
|
||||||
const functionName = me._camelToSnake(prop);
|
const functionName = me._camelToSnake(prop);
|
||||||
return me.call(functionName, ...args);
|
if(me._debug){
|
||||||
|
const call = {}
|
||||||
|
call[functionName] = args
|
||||||
|
console.debug(call)
|
||||||
|
}
|
||||||
|
return me.call(functionName, ...args);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
DEFAULT_LIMIT = 30
|
DEFAULT_LIMIT = 30
|
||||||
import typing
|
import typing
|
||||||
|
import asyncio
|
||||||
from snek.system.model import BaseModel
|
from snek.system.model import BaseModel
|
||||||
|
|
||||||
|
|
||||||
@@ -19,6 +19,13 @@ class BaseMapper:
|
|||||||
def db(self):
|
def db(self):
|
||||||
return self.app.db
|
return self.app.db
|
||||||
|
|
||||||
|
@property
|
||||||
|
def loop(self):
|
||||||
|
return asyncio.get_event_loop()
|
||||||
|
|
||||||
|
async def run_in_executor(self, func, *args, **kwargs):
|
||||||
|
return await self.loop.run_in_executor(None, lambda: func(*args, **kwargs))
|
||||||
|
|
||||||
async def new(self):
|
async def new(self):
|
||||||
return self.model_class(mapper=self, app=self.app)
|
return self.model_class(mapper=self, app=self.app)
|
||||||
|
|
||||||
@@ -29,7 +36,8 @@ class BaseMapper:
|
|||||||
async def get(self, uid: str = None, **kwargs) -> BaseModel:
|
async def get(self, uid: str = None, **kwargs) -> BaseModel:
|
||||||
if uid:
|
if uid:
|
||||||
kwargs["uid"] = uid
|
kwargs["uid"] = uid
|
||||||
record = self.table.find_one(**kwargs)
|
|
||||||
|
record = await self.run_in_executor(self.table.find_one,**kwargs)
|
||||||
if not record:
|
if not record:
|
||||||
return None
|
return None
|
||||||
record = dict(record)
|
record = dict(record)
|
||||||
@@ -40,31 +48,31 @@ class BaseMapper:
|
|||||||
return await self.model_class.from_record(mapper=self, record=record)
|
return await self.model_class.from_record(mapper=self, record=record)
|
||||||
|
|
||||||
async def exists(self, **kwargs):
|
async def exists(self, **kwargs):
|
||||||
return self.table.exists(**kwargs)
|
return await self.run_in_executor(self.table.exists,**kwargs)
|
||||||
|
|
||||||
async def count(self, **kwargs) -> int:
|
async def count(self, **kwargs) -> int:
|
||||||
return self.table.count(**kwargs)
|
return await self.run_in_executor(self.table.count, **kwargs)
|
||||||
|
|
||||||
async def save(self, model: BaseModel) -> bool:
|
async def save(self, model: BaseModel) -> bool:
|
||||||
if not model.record.get("uid"):
|
if not model.record.get("uid"):
|
||||||
raise Exception(f"Attempt to save without uid: {model.record}.")
|
raise Exception(f"Attempt to save without uid: {model.record}.")
|
||||||
model.updated_at.update()
|
model.updated_at.update()
|
||||||
return self.table.upsert(model.record, ["uid"])
|
return await self.run_in_executor(self.table.upsert, model.record, ["uid"])
|
||||||
|
|
||||||
async def find(self, **kwargs) -> typing.AsyncGenerator:
|
async def find(self, **kwargs) -> typing.AsyncGenerator:
|
||||||
if not kwargs.get("_limit"):
|
if not kwargs.get("_limit"):
|
||||||
kwargs["_limit"] = self.default_limit
|
kwargs["_limit"] = self.default_limit
|
||||||
for record in self.table.find(**kwargs):
|
for record in await self.run_in_executor(self.table.find, **kwargs):
|
||||||
model = await self.new()
|
model = await self.new()
|
||||||
for key, value in record.items():
|
for key, value in record.items():
|
||||||
model[key] = value
|
model[key] = value
|
||||||
yield model
|
yield model
|
||||||
|
|
||||||
async def query(self, sql, *args):
|
async def query(self, sql, *args):
|
||||||
for record in self.db.query(sql, *args):
|
for record in await self.run_in_executor(self.db.query,sql, *args):
|
||||||
yield dict(record)
|
yield dict(record)
|
||||||
|
|
||||||
async def delete(self, **kwargs) -> int:
|
async def delete(self, **kwargs) -> int:
|
||||||
if not kwargs or not isinstance(kwargs, dict):
|
if not kwargs or not isinstance(kwargs, dict):
|
||||||
raise Exception("Can't execute delete with no filter.")
|
raise Exception("Can't execute delete with no filter.")
|
||||||
return self.table.delete(**kwargs)
|
return await self.run_in_executor(self.table.delete, **kwargs)
|
||||||
|
|||||||
+156
-4
@@ -1,9 +1,11 @@
|
|||||||
import re
|
|
||||||
from urllib.parse import urlparse, parse_qs
|
|
||||||
from types import SimpleNamespace
|
|
||||||
|
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
import re
|
||||||
|
from functools import lru_cache
|
||||||
|
from types import SimpleNamespace
|
||||||
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
from app.cache import time_cache
|
||||||
import emoji
|
import emoji
|
||||||
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from jinja2 import TemplateSyntaxError, nodes
|
from jinja2 import TemplateSyntaxError, nodes
|
||||||
from jinja2.ext import Extension
|
from jinja2.ext import Extension
|
||||||
@@ -231,6 +233,153 @@ def linkify_https(text):
|
|||||||
return set_link_target_blank(str(soup))
|
return set_link_target_blank(str(soup))
|
||||||
|
|
||||||
|
|
||||||
|
@time_cache(timeout=60*60)
|
||||||
|
def get_url_content(url):
|
||||||
|
try:
|
||||||
|
response = requests.get(url, timeout=5)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.text
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error fetching {url}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def embed_url(text):
|
||||||
|
soup = BeautifulSoup(text, "html.parser")
|
||||||
|
|
||||||
|
attachments = {}
|
||||||
|
|
||||||
|
for element in soup.find_all("a"):
|
||||||
|
if "href" in element.attrs and element.attrs["href"].startswith("http"):
|
||||||
|
page_url = urlparse(element.attrs["href"])
|
||||||
|
page = get_url_content(element.attrs["href"])
|
||||||
|
if page:
|
||||||
|
parsed_page = BeautifulSoup(page, "html.parser")
|
||||||
|
head_info = parsed_page.find("head")
|
||||||
|
if head_info:
|
||||||
|
|
||||||
|
def get_element_options(
|
||||||
|
elem=None, meta=None, ograph=None, twitter=None
|
||||||
|
):
|
||||||
|
if twitter:
|
||||||
|
tw_tag = head_info.find(
|
||||||
|
"meta", attrs={"name": "twitter:" + twitter}
|
||||||
|
) or head_info.find(
|
||||||
|
"meta", attrs={"property": "twitter:" + twitter}
|
||||||
|
)
|
||||||
|
if tw_tag:
|
||||||
|
return tw_tag.get("content", tw_tag.get("value", None))
|
||||||
|
|
||||||
|
if ograph:
|
||||||
|
og_tag = head_info.find(
|
||||||
|
"meta", attrs={"property": "og:" + ograph}
|
||||||
|
) or head_info.find("meta", attrs={"name": "og:" + ograph})
|
||||||
|
if og_tag:
|
||||||
|
return og_tag.get("content", og_tag.get("value", None))
|
||||||
|
|
||||||
|
if meta:
|
||||||
|
meta_tag = head_info.find(
|
||||||
|
"meta", attrs={"name": meta}
|
||||||
|
) or head_info.find("meta", attrs={"property": meta})
|
||||||
|
if meta_tag:
|
||||||
|
return meta_tag.get(
|
||||||
|
"content", meta_tag.get("value", None)
|
||||||
|
)
|
||||||
|
|
||||||
|
if elem:
|
||||||
|
elem_tag = head_info.find(elem)
|
||||||
|
if elem_tag:
|
||||||
|
return elem_tag.text
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
original_link_name = element.attrs["href"]
|
||||||
|
|
||||||
|
if original_link_name in attachments:
|
||||||
|
continue
|
||||||
|
|
||||||
|
page_name = (
|
||||||
|
get_element_options("title", "title", "title", "title")
|
||||||
|
or page_url.netloc
|
||||||
|
)
|
||||||
|
page_site = (
|
||||||
|
get_element_options(None, "site", "site", "site")
|
||||||
|
or page_url.netloc
|
||||||
|
)
|
||||||
|
page_description = get_element_options(
|
||||||
|
None, "description", "description", "description"
|
||||||
|
)
|
||||||
|
page_image = get_element_options(None, "image", "image", "image")
|
||||||
|
page_image_alt = get_element_options(
|
||||||
|
None, "image:alt", "image:alt", "image:alt"
|
||||||
|
)
|
||||||
|
page_video = get_element_options(None, "video", "video", "video")
|
||||||
|
page_audio = get_element_options(None, "audio", "audio", "audio")
|
||||||
|
|
||||||
|
preview_size = (
|
||||||
|
get_element_options(None, None, None, "card")
|
||||||
|
or "summary_large_image"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
attachment_base = BeautifulSoup(str(element), "html.parser")
|
||||||
|
attachments[original_link_name] = attachment_base
|
||||||
|
|
||||||
|
attachment = next(attachment_base.children)
|
||||||
|
|
||||||
|
attachment.clear()
|
||||||
|
attachment.attrs["class"] = "embed-url-link"
|
||||||
|
|
||||||
|
render_element = attachment
|
||||||
|
|
||||||
|
if page_image:
|
||||||
|
image_template = f'<span><img src="{page_image}" alt="{page_image_alt or page_name}" title="{page_name}" width="420" height="240" /></span>'
|
||||||
|
render_element.append(
|
||||||
|
BeautifulSoup(image_template, "html.parser")
|
||||||
|
)
|
||||||
|
if page_video:
|
||||||
|
video_template = f'<video controls><source src="{page_video}">Your browser does not support the video tag.</video>'
|
||||||
|
render_element.append(
|
||||||
|
BeautifulSoup(video_template, "html.parser")
|
||||||
|
)
|
||||||
|
if page_audio:
|
||||||
|
audio_template = f'<audio controls><source src="{page_audio}">Your browser does not support the audio tag.</audio>'
|
||||||
|
render_element.append(
|
||||||
|
BeautifulSoup(audio_template, "html.parser")
|
||||||
|
)
|
||||||
|
|
||||||
|
description_element_base = BeautifulSoup(
|
||||||
|
"<span class='description'></span>", "html.parser"
|
||||||
|
)
|
||||||
|
description_element = next(description_element_base.children)
|
||||||
|
description_element.append(
|
||||||
|
BeautifulSoup(
|
||||||
|
f'<p class="page-site">{page_site}</p>',
|
||||||
|
"html.parser",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
description_element.append(
|
||||||
|
BeautifulSoup(f'<strong class="page-name">{page_name}</strong>', "html.parser")
|
||||||
|
)
|
||||||
|
|
||||||
|
description_element.append(
|
||||||
|
BeautifulSoup(f"<p class='page-description'>{page_description or "No description available."}</p>", "html.parser")
|
||||||
|
)
|
||||||
|
|
||||||
|
description_element.append(
|
||||||
|
BeautifulSoup(f"<p class='page-original-link'>{original_link_name}</p>", "html.parser")
|
||||||
|
)
|
||||||
|
|
||||||
|
render_element.append(description_element_base)
|
||||||
|
|
||||||
|
|
||||||
|
for attachment in attachments.values():
|
||||||
|
soup.append(attachment)
|
||||||
|
|
||||||
|
return str(soup)
|
||||||
|
|
||||||
|
|
||||||
class EmojiExtension(Extension):
|
class EmojiExtension(Extension):
|
||||||
tags = {"emoji"}
|
tags = {"emoji"}
|
||||||
|
|
||||||
@@ -276,6 +425,9 @@ class LinkifyExtension(Extension):
|
|||||||
result = embed_youtube(result)
|
result = embed_youtube(result)
|
||||||
|
|
||||||
result = enrich_image_rendering(result)
|
result = enrich_image_rendering(result)
|
||||||
|
|
||||||
|
result = embed_url(result)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="message-list-bottom"></div>
|
<div class="message-list-bottom"></div>
|
||||||
</message-list>
|
</message-list>
|
||||||
<chat-input live-type="false" channel="{{ channel.uid.value }}"></chat-input>
|
<chat-input live-type="true" channel="{{ channel.uid.value }}"></chat-input>
|
||||||
</section>
|
</section>
|
||||||
{% include "dialog_help.html" %}
|
{% include "dialog_help.html" %}
|
||||||
{% include "dialog_online.html" %}
|
{% include "dialog_online.html" %}
|
||||||
@@ -157,6 +157,10 @@ app.ws.addEventListener("refresh", (data) => {
|
|||||||
app.starField.showNotify(data.message);
|
app.starField.showNotify(data.message);
|
||||||
setTimeout(() => window.location.reload(), 4000);
|
setTimeout(() => window.location.reload(), 4000);
|
||||||
});
|
});
|
||||||
|
app.ws.addEventListener("stars_render", (data)=>{
|
||||||
|
app.starField.renderWord(data.message, { rainbow: true, resolution: 8 });
|
||||||
|
setTimeout(() => app.starField.shuffleAll(5000), 10000);
|
||||||
|
})
|
||||||
app.ws.addEventListener("deployed", (data) => {
|
app.ws.addEventListener("deployed", (data) => {
|
||||||
app.starField.renderWord("Deployed", { rainbow: true, resolution: 8 });
|
app.starField.renderWord("Deployed", { rainbow: true, resolution: 8 });
|
||||||
setTimeout(() => app.starField.shuffleAll(5000), 10000);
|
setTimeout(() => app.starField.shuffleAll(5000), 10000);
|
||||||
@@ -167,7 +171,9 @@ app.ws.addEventListener("starfield.render_word", (data) => {
|
|||||||
|
|
||||||
// --- Channel message event ---
|
// --- Channel message event ---
|
||||||
app.addEventListener("channel-message", (data) => {
|
app.addEventListener("channel-message", (data) => {
|
||||||
|
|
||||||
let display = data.text && data.text.trim() ? 'block' : 'none';
|
let display = data.text && data.text.trim() ? 'block' : 'none';
|
||||||
|
|
||||||
if (data.channel_uid !== channelUid) {
|
if (data.channel_uid !== channelUid) {
|
||||||
if (!isMentionForSomeoneElse(data.message)) {
|
if (!isMentionForSomeoneElse(data.message)) {
|
||||||
channelSidebar.notify(data);
|
channelSidebar.notify(data);
|
||||||
@@ -179,12 +185,22 @@ app.addEventListener("channel-message", (data) => {
|
|||||||
if (isMentionToMe(data.message)) {
|
if (isMentionToMe(data.message)) {
|
||||||
app.playSound("mention");
|
app.playSound("mention");
|
||||||
} else if (!isMentionForSomeoneElse(data.message)) {
|
} else if (!isMentionForSomeoneElse(data.message)) {
|
||||||
app.playSound("message");
|
if(data.is_final){
|
||||||
|
app.playSound("message");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const lastElement = messagesContainer.querySelector(".message-list-bottom");
|
const lastElement = messagesContainer.querySelector(".message-list-bottom");
|
||||||
const doScrollDown = messagesContainer.isScrolledToBottom();
|
const doScrollDown = messagesContainer.isScrolledToBottom();
|
||||||
|
|
||||||
|
const oldMessage = messagesContainer.querySelector(`.message[data-uid="${data.uid}"]`);
|
||||||
|
if (oldMessage) {
|
||||||
|
oldMessage.remove();
|
||||||
|
}
|
||||||
|
|
||||||
const message = document.createElement("div");
|
const message = document.createElement("div");
|
||||||
|
|
||||||
|
|
||||||
message.innerHTML = data.html;
|
message.innerHTML = data.html;
|
||||||
message.style.display = display;
|
message.style.display = display;
|
||||||
messagesContainer.insertBefore(message.firstChild, lastElement);
|
messagesContainer.insertBefore(message.firstChild, lastElement);
|
||||||
@@ -210,6 +226,18 @@ document.addEventListener('keydown', function(event) {
|
|||||||
loadExtra();
|
loadExtra();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(event.key == 'i' && !chatInputField.isActive()) {
|
||||||
|
event.preventDefault();
|
||||||
|
chatInputField.focus();
|
||||||
|
}
|
||||||
|
if(event.key == 'r' && !chatInputField.isActive()) {
|
||||||
|
event.preventDefault();
|
||||||
|
const replyLinks = document.querySelectorAll('.time a')
|
||||||
|
if(replyLinks.length){
|
||||||
|
const replyLink = replyLinks[replyLinks.length - 1]
|
||||||
|
replyLink.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
if (event.shiftKey && event.key === 'G') {
|
if (event.shiftKey && event.key === 'G') {
|
||||||
if (!chatInputField.isActive()) {
|
if (!chatInputField.isActive()) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|||||||
+11
-2
@@ -196,6 +196,9 @@ class RPCView(BaseView):
|
|||||||
async def finalize_message(self, message_uid):
|
async def finalize_message(self, message_uid):
|
||||||
self._require_login()
|
self._require_login()
|
||||||
message = await self.services.channel_message.get(message_uid)
|
message = await self.services.channel_message.get(message_uid)
|
||||||
|
if not message:
|
||||||
|
return False
|
||||||
|
|
||||||
if message["user_uid"] != self.user_uid:
|
if message["user_uid"] != self.user_uid:
|
||||||
raise Exception("Not allowed")
|
raise Exception("Not allowed")
|
||||||
|
|
||||||
@@ -203,7 +206,7 @@ class RPCView(BaseView):
|
|||||||
if not message['is_final']:
|
if not message['is_final']:
|
||||||
await self.services.chat.finalize(message['uid'])
|
await self.services.chat.finalize(message['uid'])
|
||||||
|
|
||||||
return {"success": True}
|
return True
|
||||||
|
|
||||||
async def update_message_text(self,message_uid, text):
|
async def update_message_text(self,message_uid, text):
|
||||||
self._require_login()
|
self._require_login()
|
||||||
@@ -377,7 +380,13 @@ class RPCView(BaseView):
|
|||||||
return {"pong": args}
|
return {"pong": args}
|
||||||
|
|
||||||
|
|
||||||
|
async def stars_render(self, channel_uid, message):
|
||||||
|
|
||||||
|
for user in await self.get_online_users(channel_uid):
|
||||||
|
try:
|
||||||
|
await self.services.socket.send_to_user(user['uid'], dict(event="stars_render", data={"channel_uid": channel_uid, "message":message}))
|
||||||
|
except Exception as ex:
|
||||||
|
print(ex)
|
||||||
|
|
||||||
async def get(self):
|
async def get(self):
|
||||||
scheduled = []
|
scheduled = []
|
||||||
@@ -404,7 +413,7 @@ class RPCView(BaseView):
|
|||||||
"message": "Finishing deployment"}
|
"message": "Finishing deployment"}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await schedule(self.request.session.get("uid"),10,{"event": "deployed", "data": {
|
await schedule(self.request.session.get("uid"),15,{"event": "deployed", "data": {
|
||||||
"uptime": self.request.app.uptime}
|
"uptime": self.request.app.uptime}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user