From 6bb0189078d13567edb2700b0a1f63758f9e2ea9 Mon Sep 17 00:00:00 2001
From: 10G Meow <10gmeow@gmail.com>
Date: Sun, 21 Dec 2025 22:23:09 +0200
Subject: [PATCH 01/17] Confirmation dialog ux: Logout, set password and change
name
---
src/assets/translations/en.json | 4 +-
src/components/LogoutRoomDialog.vue | 9 ++-
src/components/Profile.vue | 91 +++++++++++++++--------------
3 files changed, 57 insertions(+), 47 deletions(-)
diff --git a/src/assets/translations/en.json b/src/assets/translations/en.json
index aecbbdd..2b3115d 100644
--- a/src/assets/translations/en.json
+++ b/src/assets/translations/en.json
@@ -253,6 +253,7 @@
"title": "My Profile",
"temporary_identity": "This identity is temporary. Set a password to use it again",
"set_password": "Set password",
+ "set_password_desc": "Convene does not store passwords and cannot recover them. We recommend storing your password in a secure location before clicking save.",
"change_name": "Change name",
"change_password": "Change password",
"my_rooms": "My rooms",
@@ -323,7 +324,8 @@
"copy_credentials": "Copy Username & Password",
"copied_credentials": "Copied Username & Password",
"copied_credentials_value": "Username: {userId} \nPassword: {password}",
- "copy_credentials_desc": "Your username and password are required to regain access to your chats from a new device or browser. We recommend storing these credentials in a secure location."
+ "copy_credentials_desc": "Your username and password are required to regain access to your chats from a new device or browser. We recommend storing these credentials in a secure location.",
+ "copy_credentials_desc_for_real_user": "Your username (ex: {'@user:server'}) and your password are required to regain access to your chats from a new device or browser."
},
"delete_post": {
"confirm_text": "Are you sure you want to delete this message?",
diff --git a/src/components/LogoutRoomDialog.vue b/src/components/LogoutRoomDialog.vue
index 8a9b6ff..3a24676 100644
--- a/src/components/LogoutRoomDialog.vue
+++ b/src/components/LogoutRoomDialog.vue
@@ -4,8 +4,8 @@
@click:outside="$emit('onOutsideLogoutPopupClicked')">
{{ $t("logout.confirm_text") }}
-
{{ $t("logout.copy_credentials_desc") }}
-
+ {{ copyCredentialsSubText }}
+
{{ $matrix.currentUser.is_guest ? $t("profile.set_password") : $t("profile.change_password") }}
+ {{ $t("profile.set_password_desc") }}
-
-
-
- {{
- $t("menu.cancel")
- }}
- {{ $t("global.save") }}
-
+
+
+
+ {{
+ $t("menu.cancel") }}
+
+
+ {{ $t("global.save") }}
+
+
+
@@ -180,33 +183,35 @@
class="ma-0 pa-0"
:width="$vuetify.display.smAndUp ? '940px' : '80%'"
>
-
+
{{ $t("profile.display_name") }}
-
+
-
-
-
-
- {{
- $t("menu.cancel")
- }}
- {{ $t("global.save") }}
-
+
+
+
+ {{
+ $t("menu.cancel") }}
+
+
+ {{ $t("global.save") }}
+
+
+
+
From af96e3db5f4ec5be75afa97c63ddd6028b34fad6 Mon Sep 17 00:00:00 2001
From: N-Pex
Date: Mon, 22 Dec 2025 12:31:56 +0100
Subject: [PATCH 02/17] Implement service worker offline page
Also, make sure it can be translated.
---
public/offline.html | 164 +++++++++++++++++
public/sw.js | 99 ++++++++++
src/App.vue | 27 +++
src/assets/translations/en.json | 5 +
src/assets/translations/fi.json | 5 +
src/components/MigratingView.vue | 55 ++++++
src/router/index.js | 306 +++++++++++++++++++------------
src/services/matrix.service.js | 28 ++-
src/store/index.js | 27 +++
9 files changed, 601 insertions(+), 115 deletions(-)
create mode 100644 public/offline.html
create mode 100644 src/components/MigratingView.vue
diff --git a/public/offline.html b/public/offline.html
new file mode 100644
index 0000000..0f3160a
--- /dev/null
+++ b/public/offline.html
@@ -0,0 +1,164 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Having trouble connecting?
+
Redirect to an alternate link to join the room
+
Redirect me
+
You are offline
+
+
+
+
\ No newline at end of file
diff --git a/public/sw.js b/public/sw.js
index 51c9f2d..bece099 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -1,5 +1,19 @@
var periodicSyncNewMsgReminderText;
+const OFFLINE_CACHE = `offline`;
+const offlineCacheFiles = ['offline.html','config.json'];
+
+// on install we download the routes we want to cache for offline
+self.addEventListener('install', evt => evt.waitUntil(caches.open(OFFLINE_CACHE).then(cache => {
+ console.log("SW Caching offline files");
+ self.skipWaiting();
+ return cache.addAll(offlineCacheFiles);
+})));
+
+self.addEventListener("activate", event => {
+ event.waitUntil(clients.claim());
+});
+
// Notification click event listener
self.addEventListener("notificationclick", (e) => {
e.notification.close();
@@ -40,3 +54,88 @@ self.addEventListener('periodicsync', (event) => {
event.waitUntil(checkNewMessages());
}
});
+
+self.addEventListener("fetch", (event) => {
+ if (event.request.mode === 'navigate') {
+ return event.respondWith(
+ fetch(event.request).catch((e) => {
+ console.log("OFFLINE, serve offline page", e);
+ return serveOfflinePage();
+ }));
+ } else if (event.request.url.endsWith("config.json")) {
+ return fetch(event.request)
+ .then((response) => {
+ console.log("Caching a new version of config.json");
+ let responseClone = response.clone();
+ caches
+ .open(OFFLINE_CACHE)
+ .then((cache) => cache.put(event.request, responseClone));
+ return response;
+ })
+ }
+});
+
+async function serveOfflinePage() {
+ let mirrorUrl = null;
+ const rConfig = await caches.match("config.json", { cacheName: OFFLINE_CACHE});
+ if (rConfig) {
+ const json = await rConfig.json();
+ const mirrors = json.mirrors;
+ if (mirrors && Array.isArray(mirrors) && mirrors.length > 0) {
+ mirrorUrl = json.mirrors[Math.floor(Math.random() * mirrors.length)];
+ }
+ }
+ const offlinePage = await caches.match("offline.html", { cacheName: OFFLINE_CACHE});
+ if (mirrorUrl && offlinePage) {
+ let text = await offlinePage.text();
+ text = text.replaceAll("", mirrorUrl);
+
+ let title = undefined;
+ let message = undefined;
+ let redirect = undefined;
+
+ await new Promise((resolve, reject) => {
+ var open = indexedDB.open("ServiceWorker", 1);
+ open.onerror = function() {
+ resolve(false);
+ }
+ open.onsuccess = function() {
+ // Start a new transaction
+ var db = open.result;
+ var tx = db.transaction("offline_strings", "readonly");
+ var store = tx.objectStore("offline_strings");
+
+ var get1 = store.get("offline_title");
+ var get2 = store.get("offline_message");
+ var get3 = store.get("offline_redirect");
+
+ get1.onsuccess = function() {
+ title = get1.result.translation;
+ };
+ get2.onsuccess = function() {
+ message = get2.result.translation;
+ };
+ get3.onsuccess = function() {
+ redirect = get3.result.translation;
+ };
+
+ // Close the db when the transaction is done
+ tx.oncomplete = function() {
+ db.close();
+ resolve(true);
+ };
+ }
+ });
+ if (title) {
+ text = text.replaceAll(/(.*?)/g, title);
+ }
+ if (message) {
+ text = text.replaceAll(/(.*?)/g, message);
+ }
+ if (redirect) {
+ text = text.replaceAll(/(.*?)/g, redirect);
+ }
+ return new Response(text, { headers: {"content-type": "text/html"}});
+ }
+ throw new Error("Offline");
+}
\ No newline at end of file
diff --git a/src/App.vue b/src/App.vue
index 9f46442..16dfc33 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -121,6 +121,33 @@ export default {
// Set language
this.$i18n.locale = this.$store.state.language || "en";
+
+ // Store offline strings.
+ // TODO - Better way to to this? The service worker needs to be able to get these, OR we need multiple translated version of offline.html
+ //
+ // Inspired by https://gist.github.com/JamesMessinger/a0d6389a5d0e3a24814b
+ var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB;
+ var open = indexedDB.open("ServiceWorker", 1);
+ // Create the schema
+ open.onupgradeneeded = () => {
+ var db = open.result;
+ var store = db.createObjectStore("offline_strings", {keyPath: "id"});
+ };
+ open.onsuccess = () => {
+ // Start a new transaction
+ var db = open.result;
+ var tx = db.transaction("offline_strings", "readwrite");
+ var store = tx.objectStore("offline_strings");
+
+ store.put({id: "offline_title", translation: this.$t("offline.offline_title")});
+ store.put({id: "offline_message", translation: this.$t("offline.offline_message")});
+ store.put({id: "offline_redirect", translation: this.$t("offline.offline_redirect")});
+
+ // Close the db when the transaction is done
+ tx.oncomplete = function() {
+ db.close();
+ };
+ }
},
showNotification() {
if(document.visibilityState === "hidden") {
diff --git a/src/assets/translations/en.json b/src/assets/translations/en.json
index 2b3115d..891fb3d 100644
--- a/src/assets/translations/en.json
+++ b/src/assets/translations/en.json
@@ -541,5 +541,10 @@
"generated_with_ai": "Generated with AI.",
"modified": "Modified.",
"older_than_n_months": "Older than {n} months."
+ },
+ "offline": {
+ "offline_title": "Having trouble connecting?",
+ "offline_message": "Redirect to an alternate link to join the room",
+ "offline_redirect": "Redirect me"
}
}
diff --git a/src/assets/translations/fi.json b/src/assets/translations/fi.json
index a017ba7..4068a93 100644
--- a/src/assets/translations/fi.json
+++ b/src/assets/translations/fi.json
@@ -259,5 +259,10 @@
},
"delete_post": {
"confirm_text_desc": "Tätä toimintoa ei voi perua."
+ },
+ "offline": {
+ "offline_title": "Ongelmia yhdistämisessä?",
+ "offline_message": "Siirry vaihtoehtoiseen linkkiin liittyäksesi huoneeseen",
+ "offline_redirect": "Siirrä minut"
}
}
diff --git a/src/components/MigratingView.vue b/src/components/MigratingView.vue
new file mode 100644
index 0000000..dc720bd
--- /dev/null
+++ b/src/components/MigratingView.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/router/index.js b/src/router/index.js
index 863587f..513a22d 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,126 +1,141 @@
-import Home from '../components/Home.vue'
-import Chat from '../components/Chat.vue'
-import Join from '../components/Join.vue'
-import Login from '../components/Login.vue'
-import Profile from '../components/Profile.vue'
-import CreateRoom from '../components/CreateRoom.vue'
-import GetLink from '../components/GetLink.vue'
-import Create from '../components/Create.vue'
-import CreateChannel from '../components/CreateChannel.vue'
-import CreateFileDrop from '../components/CreateFileDrop.vue'
-import User from '../models/user'
-import util from '../plugins/utils'
-import { createRouter, createWebHashHistory } from 'vue-router'
-import { defineAsyncComponent } from 'vue'
+import Home from "../components/Home.vue";
+import Chat from "../components/Chat.vue";
+import Join from "../components/Join.vue";
+import Login from "../components/Login.vue";
+import Profile from "../components/Profile.vue";
+import CreateRoom from "../components/CreateRoom.vue";
+import GetLink from "../components/GetLink.vue";
+import Create from "../components/Create.vue";
+import CreateChannel from "../components/CreateChannel.vue";
+import CreateFileDrop from "../components/CreateFileDrop.vue";
+import User from "../models/user";
+import util from "../plugins/utils";
+import { createRouter, createWebHashHistory } from "vue-router";
+import { defineAsyncComponent } from "vue";
+import { STORE_KEY_SETTINGS, STORE_KEY_USER } from "../store";
const routes = [
{
- path: '/',
- name: 'Home',
- component: Home
+ path: "/",
+ name: "Home",
+ component: Home,
},
{
- path: '/room/:join(join/)?:roomId?',
- name: 'Chat',
+ path: "/room/:join(join/)?:roomId?",
+ name: "Chat",
component: Chat,
meta: {
includeRoom: true,
- includeFavicon: true
+ includeFavicon: true,
},
},
{
- path: '/info',
- name: 'RoomInfo',
- component: () => import('../components/RoomInfo.vue'),
+ path: "/info",
+ name: "RoomInfo",
+ component: () => import("../components/RoomInfo.vue"),
props: true,
meta: {
- title: 'Info',
+ title: "Info",
includeRoom: true,
- includeFavicon: true
- }
+ includeFavicon: true,
+ },
},
{
- path: '/profile',
- name: 'Profile',
+ path: "/profile",
+ name: "Profile",
component: Profile,
meta: {
- title: 'Profile',
- includeFavicon: true
- }
+ title: "Profile",
+ includeFavicon: true,
+ },
},
{
- path: '/createroom',
- name: 'CreateRoom',
+ path: "/createroom",
+ name: "CreateRoom",
component: CreateRoom,
meta: {
- title: 'Create room'
- }
+ title: "Create room",
+ },
},
{
- path: '/getlink',
- name: 'GetLink',
+ path: "/getlink",
+ name: "GetLink",
component: GetLink,
},
{
- path: '/create',
- name: 'Create',
+ path: "/create",
+ name: "Create",
component: Create,
},
{
- path: '/createchannel',
- name: 'CreateChannel',
+ path: "/createchannel",
+ name: "CreateChannel",
component: CreateChannel,
},
{
- path: '/createfiledrop',
- name: 'CreateFileDrop',
+ path: "/createfiledrop",
+ name: "CreateFileDrop",
component: CreateFileDrop,
},
{
- path: '/login',
- name: 'Login',
+ path: "/login",
+ name: "Login",
component: Login,
- props: true
+ props: true,
},
{
- path: '/join/:join(join/)?:roomId?',
- name: 'Join',
+ path: "/join/:join(join/)?:roomId?",
+ name: "Join",
component: Join,
- props: route => {
+ props: (route) => {
return {
- roomNeedsKnock: (route.query.knock ? true : false),
- roomDisplayName: route.query.roomName
- }
- }
+ roomNeedsKnock: route.query.knock ? true : false,
+ roomDisplayName: route.query.roomName,
+ };
+ },
},
{
- path: '/user/:userId?',
- name: 'User',
- component: Join
+ path: "/user/:userId?",
+ name: "User",
+ component: Join,
},
{
- path: '/invite/:roomId?',
- name: 'Invite',
- component: defineAsyncComponent(() => import('../components/Invite.vue')),
+ path: "/invite/:roomId?",
+ name: "Invite",
+ component: defineAsyncComponent(() => import("../components/Invite.vue")),
meta: {
- title: 'Add Friends'
- }
+ title: "Add Friends",
+ },
},
{
- path: '/goodbye',
- name: 'Goodbye',
- component: defineAsyncComponent(() => import('../components/QuoteView.vue')),
- props: true
- }
-]
+ path: "/goodbye",
+ name: "Goodbye",
+ component: defineAsyncComponent(() => import("../components/QuoteView.vue")),
+ props: true,
+ },
+ {
+ path: "/migrating",
+ name: "Migrating",
+ component: defineAsyncComponent(() => import("../components/MigratingView.vue")),
+ props: false,
+ },
+];
const router = createRouter({
history: createWebHashHistory(),
- routes: routes
+ routes: routes,
});
router.beforeEach((to, from, next) => {
- const publicPages = ['/login', '/createroom', '/getlink', '/create', '/createchannel', '/createfiledrop'];
+ const publicPages = [
+ "/login",
+ "/createroom",
+ "/getlink",
+ "/create",
+ "/createchannel",
+ "/createfiledrop",
+ "/migrating",
+ ];
var authRequired = !publicPages.includes(to.path);
const loggedIn = router.app.$store.state.auth.user;
@@ -129,14 +144,46 @@ router.beforeEach((to, from, next) => {
const lang = to.query.lang;
// Check if valid translation
if (router.app.$i18n.messages[lang]) {
- router.app.$store.commit('setLanguage', lang);
+ router.app.$store.commit("setLanguage", lang);
if (router.app.$i18n) {
router.app.$i18n.locale = to.query.lang;
}
}
}
- if (to.name == 'Chat' || to.name == 'Join') {
+ if (to.query && to.query.migrate) {
+ try {
+ if (window.opener) {
+ console.log("Start listening for migration messages");
+ window.addEventListener("message", function (e) {
+ // if (e.origin !== origin) { // TODO - What should we check here?
+ // return
+ // }
+ try {
+ const data = JSON.parse(e.data);
+ if (data !== null) {
+ router.app.$store
+ .dispatch("migrate", data)
+ .then(() => {
+ console.log("Migration success");
+ })
+ .catch((error) => {
+ console.log("Migration error", error);
+ })
+ .finally(() => {
+ window.opener.postMessage(JSON.stringify({ cmd: "migrationDone" }), "*");
+ router.app.$navigation.push({ name: "Home" }, -1);
+ });
+ }
+ } catch (error) {}
+ });
+ window.opener.postMessage(JSON.stringify({ cmd: "getMigrationData" }), "*");
+ }
+ return next("/migrating");
+ } catch (error) {}
+ }
+
+ if (to.name == "Chat" || to.name == "Join") {
if (!to.params.roomId && to.hash) {
// Public rooms start with '#', confuses the router. If hash but no roomId param, set it.
to.params.roomId = to.hash;
@@ -147,62 +194,84 @@ router.beforeEach((to, from, next) => {
//Invite to public room
authRequired = false;
}
- } else if (to.name == 'Home') {
+ } else if (to.name == "Home") {
if (to.params.roomId !== undefined) {
const roomId = to.params.roomId ? util.sanitizeRoomId(to.params.roomId) : null;
router.app.$matrix.setCurrentRoomId(roomId);
}
- } else if (to.name == 'User') {
+ } else if (to.name == "User") {
if (to.params.userId) {
let roomId = util.sanitizeUserId(to.params.userId);
if (roomId && !roomId.startsWith("@")) {
// Not a full username. Assume local name on this server.
- return router.app.$config.load().then((config) => {
- const domain = config.defaultMatrixDomainPart;
- if (!domain) throw new Error("No domain part for user invite!");
- roomId = "@" + roomId + ":" + domain;
- router.app.$matrix.setCurrentRoomId(roomId);
- }).catch(err => console.error(err)).finally(() => next());
+ return router.app.$config
+ .load()
+ .then((config) => {
+ const domain = config.defaultMatrixDomainPart;
+ if (!domain) throw new Error("No domain part for user invite!");
+ roomId = "@" + roomId + ":" + domain;
+ router.app.$matrix.setCurrentRoomId(roomId);
+ })
+ .catch((err) => console.error(err))
+ .finally(() => next());
} else {
router.app.$matrix.setCurrentRoomId(roomId);
authRequired = false;
}
}
- } else if (to.name == 'Invite') {
+ } else if (to.name == "Invite") {
if (to.params.roomId) {
const roomId = util.sanitizeRoomId(to.params.roomId);
router.app.$matrix.setCurrentRoomId(roomId);
}
- } else if (to.name == 'CreateRoom') {
- return router.app.$config.load().then((config) => {
- if (config.hide_add_room_on_home || !config.roomTypes.includes("group_chat")) {
- next('/');
- } else {
- next();
- }
- }).catch(err => { console.error(err); next('/'); });
- } else if (to.name == 'CreateChannel') {
- return router.app.$config.load().then((config) => {
- if (!config.roomTypes.includes("channel")) {
- next('/');
- } else {
- next();
- }
- }).catch(err => { console.error(err); next('/'); });
- } else if (to.name == 'CreateFileDrop') {
- return router.app.$config.load().then((config) => {
- if (!config.roomTypes.includes("file_drop")) {
- next('/');
- } else {
- next();
- }
- }).catch(err => { console.error(err); next('/'); });
+ } else if (to.name == "CreateRoom") {
+ return router.app.$config
+ .load()
+ .then((config) => {
+ if (config.hide_add_room_on_home || !config.roomTypes.includes("group_chat")) {
+ next("/");
+ } else {
+ next();
+ }
+ })
+ .catch((err) => {
+ console.error(err);
+ next("/");
+ });
+ } else if (to.name == "CreateChannel") {
+ return router.app.$config
+ .load()
+ .then((config) => {
+ if (!config.roomTypes.includes("channel")) {
+ next("/");
+ } else {
+ next();
+ }
+ })
+ .catch((err) => {
+ console.error(err);
+ next("/");
+ });
+ } else if (to.name == "CreateFileDrop") {
+ return router.app.$config
+ .load()
+ .then((config) => {
+ if (!config.roomTypes.includes("file_drop")) {
+ next("/");
+ } else {
+ next();
+ }
+ })
+ .catch((err) => {
+ console.error(err);
+ next("/");
+ });
}
// trying to access a restricted page + not logged in
// redirect to login page
if (authRequired && !loggedIn) {
- next('/login');
+ next("/login");
} else {
next();
}
@@ -225,12 +294,21 @@ router.getRoomLink = function (alias, roomId, roomName, mode, autojoin, knock, m
const autoJoinSegment = autojoin ? "join/" : "";
let queryString = "";
if (Object.entries(params).length > 0) {
- queryString = "?" + Object.entries(params)
- .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
- .join('&')
+ queryString =
+ "?" +
+ Object.entries(params)
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
+ .join("&");
}
- return (mirror ? (window.location.protocol + "//" + mirror) : window.location.origin) + window.location.pathname + "#/room/" + autoJoinSegment + encodeURIComponent(util.sanitizeRoomId(alias || roomId)) + queryString;
-}
+ return (
+ (mirror ? window.location.protocol + "//" + mirror : window.location.origin) +
+ window.location.pathname +
+ "#/room/" +
+ autoJoinSegment +
+ encodeURIComponent(util.sanitizeRoomId(alias || roomId)) +
+ queryString
+ );
+};
router.getDMLink = function (user, config, mirror) {
let userId = user.user_id;
@@ -238,7 +316,9 @@ router.getDMLink = function (user, config, mirror) {
// Using default server, don't include it in the link
userId = User.localPart(user.user_id);
}
- return `${(mirror ? (window.location.protocol + "//" + mirror) : window.location.origin) + window.location.pathname}#/user/${encodeURIComponent(userId)}`
-}
+ return `${
+ (mirror ? window.location.protocol + "//" + mirror : window.location.origin) + window.location.pathname
+ }#/user/${encodeURIComponent(userId)}`;
+};
-export default router
+export default router;
diff --git a/src/services/matrix.service.js b/src/services/matrix.service.js
index cbe284c..5f21070 100644
--- a/src/services/matrix.service.js
+++ b/src/services/matrix.service.js
@@ -141,7 +141,27 @@ export default {
}
return this.legacyCryptoStore;
},
- login(user, registrationFlowHandler, createUser = false) {
+ migrate(user) {
+ return util.getMatrixBaseUrl(user, this.$config)
+ .then((baseUrl) => {
+ console.log("Create temp client", user);
+ const tempMatrixClient = sdk.createClient({
+ baseUrl: baseUrl,
+ idBaseUrl: this.$config.identityServer,
+ });
+ user.device_id = tempMatrixClient.getSessionId();
+ return this.refreshAccessToken(tempMatrixClient, user.refresh_token);
+ }).then(() => {
+ console.log("Refresh done, normal login");
+ return this.login(user, undefined, false, true);
+ });
+ },
+ login(user, registrationFlowHandler, createUser = false, isMigration = false) {
+ if (!isMigration && window.location.href.includes("migrate=1")) {
+ console.log("Migrate, so no login!");
+ return Promise.reject("Migrating, so no login");
+ }
+
return util.getMatrixBaseUrl(user, this.$config).then((baseUrl) => {
const tempMatrixClient = sdk.createClient({
baseUrl: baseUrl,
@@ -510,12 +530,16 @@ export default {
},
onSessionRefresh(refreshToken) {
+ return this.refreshAccessToken(this.matrixClient, refreshToken);
+ },
+
+ refreshAccessToken(matrixClient, refreshToken) {
if (this.tokenRefreshPromise) {
return this.tokenRefreshPromise;
}
const now = Date.now();
this.tokenRefreshPromise =
- this.matrixClient.http.request(sdk.Method.Post, "/refresh", undefined, { refresh_token: refreshToken}, { prefix: sdk.ClientPrefix.V3, inhibitLogoutEmit: true }).then((result) => {
+ matrixClient.http.request(sdk.Method.Post, "/refresh", undefined, { refresh_token: refreshToken}, { prefix: sdk.ClientPrefix.V3, inhibitLogoutEmit: true }).then((result) => {
// Store new one!
var user = this.$store.state.auth.user;
user.access_token = result.access_token;
diff --git a/src/store/index.js b/src/store/index.js
index 1b3c597..16fc704 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -136,6 +136,33 @@ const store = createStore({
this.$matrix.logout();
commit('logout');
},
+ migrate({ state }, migrationData) {
+ if (migrationData && migrationData.type) {
+ let storage = migrationData.type == "session" ? sessionStorage : localStorage;
+ if (migrationData.settings) {
+ try {
+ const settings = JSON.parse(migrationData.settings);
+ storage.setItem(STORE_KEY_SETTINGS, migrationData.settings);
+ state.useLocalStorage = migrationData.type != "session";
+ } catch (error) {
+ console.error("Failed to migrate settings", error);
+ }
+ }
+ if (migrationData.user) {
+ const user = JSON.parse(migrationData.user);
+ state.auth.user = user;
+ return this.$matrix.migrate(user).then(
+ (user) => {
+ return Promise.resolve(user);
+ },
+ (error) => {
+ return Promise.reject(error);
+ }
+ );
+ }
+ }
+ return Promise.reject("Migration error");
+ }
},
getters: {
storage: state => {
From f7032f659acf16bc9a416c97720dc1a658f56549 Mon Sep 17 00:00:00 2001
From: N-Pex
Date: Mon, 22 Dec 2025 12:40:39 +0100
Subject: [PATCH 03/17] Build 92
---
package.json | 2 +-
package.json.bak | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index 21aea58..61c66de 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "keanuapp-weblite",
- "version": "0.1.91",
+ "version": "0.1.92",
"private": true,
"scripts": {
"dev": "vite",
diff --git a/package.json.bak b/package.json.bak
index a0b65f9..21aea58 100644
--- a/package.json.bak
+++ b/package.json.bak
@@ -1,6 +1,6 @@
{
"name": "keanuapp-weblite",
- "version": "0.1.90",
+ "version": "0.1.91",
"private": true,
"scripts": {
"dev": "vite",
From edc1824130fbb55f06d7b008ec9e6c2d305497a7 Mon Sep 17 00:00:00 2001
From: N-Pex
Date: Mon, 22 Dec 2025 13:10:10 +0100
Subject: [PATCH 04/17] Prepend protocol to redirection link if needed
---
public/offline.html | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/public/offline.html b/public/offline.html
index 0f3160a..02fefda 100644
--- a/public/offline.html
+++ b/public/offline.html
@@ -34,7 +34,11 @@
function go() {
if (navigator.onLine) {
- windowProxy = window.open("/#/?migrate=1", "_blank");
+ let url = "/#/?migrate=1";
+ if (!url.includes("://")) {
+ url = window.location.protocol + "//" + url;
+ }
+ windowProxy = window.open(url, "_blank");
}
}
@@ -68,7 +72,11 @@
} else if (data !== null && data.cmd == "migrationDone") {
if (windowProxy) {
windowProxy.close();
- window.location.href = "/#/";
+ let url = "/#/";
+ if (!url.includes("://")) {
+ url = window.location.protocol + "//" + url;
+ }
+ window.location.href = url;
}
}
} catch (error) {
From f41aec59bbcae9e05779a555e778169709cc0757 Mon Sep 17 00:00:00 2001
From: N-Pex
Date: Mon, 22 Dec 2025 13:10:18 +0100
Subject: [PATCH 05/17] Build 93
---
package.json | 2 +-
package.json.bak | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index 61c66de..6ff2945 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "keanuapp-weblite",
- "version": "0.1.92",
+ "version": "0.1.93",
"private": true,
"scripts": {
"dev": "vite",
diff --git a/package.json.bak b/package.json.bak
index 21aea58..61c66de 100644
--- a/package.json.bak
+++ b/package.json.bak
@@ -1,6 +1,6 @@
{
"name": "keanuapp-weblite",
- "version": "0.1.91",
+ "version": "0.1.92",
"private": true,
"scripts": {
"dev": "vite",
From 47ebc55f808f34c18a3b455d523c8e9b9736d8fb Mon Sep 17 00:00:00 2001
From: N-Pex
Date: Mon, 22 Dec 2025 13:23:43 +0100
Subject: [PATCH 06/17] Build 94
Preserve the path name for mirror links
---
package.json | 2 +-
package.json.bak | 2 +-
public/offline.html | 4 ++--
public/sw.js | 1 +
4 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/package.json b/package.json
index 6ff2945..af6d380 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "keanuapp-weblite",
- "version": "0.1.93",
+ "version": "0.1.94",
"private": true,
"scripts": {
"dev": "vite",
diff --git a/package.json.bak b/package.json.bak
index 61c66de..6ff2945 100644
--- a/package.json.bak
+++ b/package.json.bak
@@ -1,6 +1,6 @@
{
"name": "keanuapp-weblite",
- "version": "0.1.92",
+ "version": "0.1.93",
"private": true,
"scripts": {
"dev": "vite",
diff --git a/public/offline.html b/public/offline.html
index 02fefda..66e64d4 100644
--- a/public/offline.html
+++ b/public/offline.html
@@ -34,7 +34,7 @@
function go() {
if (navigator.onLine) {
- let url = "/#/?migrate=1";
+ let url = "" + window.location.pathname + "#/?migrate=1";
if (!url.includes("://")) {
url = window.location.protocol + "//" + url;
}
@@ -72,7 +72,7 @@
} else if (data !== null && data.cmd == "migrationDone") {
if (windowProxy) {
windowProxy.close();
- let url = "/#/";
+ let url = "" + window.location.pathname + "#/";
if (!url.includes("://")) {
url = window.location.protocol + "//" + url;
}
diff --git a/public/sw.js b/public/sw.js
index bece099..233a802 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -126,6 +126,7 @@ async function serveOfflinePage() {
};
}
});
+
if (title) {
text = text.replaceAll(/(.*?)/g, title);
}
From bda93a71e4d72c4a843723f323e6500c32012858 Mon Sep 17 00:00:00 2001
From: N-Pex
Date: Tue, 23 Dec 2025 15:11:50 +0100
Subject: [PATCH 07/17] Fix selecting language via ?lang= query param
---
src/router/index.js | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/router/index.js b/src/router/index.js
index 513a22d..eeabf54 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -143,11 +143,9 @@ router.beforeEach((to, from, next) => {
// Set language via query param
const lang = to.query.lang;
// Check if valid translation
- if (router.app.$i18n.messages[lang]) {
+ if (router.app.$i18n && router.app.$i18n.global && router.app.$i18n.global.messages[lang]) {
router.app.$store.commit("setLanguage", lang);
- if (router.app.$i18n) {
- router.app.$i18n.locale = to.query.lang;
- }
+ router.app.$i18n.global.locale = to.query.lang;
}
}
From 1fb26d6c8484cdabd172814e84023b9d7aa8ff1a Mon Sep 17 00:00:00 2001
From: N-Pex
Date: Tue, 23 Dec 2025 15:12:29 +0100
Subject: [PATCH 08/17] Build 95
---
package.json | 2 +-
package.json.bak | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index af6d380..69f886f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "keanuapp-weblite",
- "version": "0.1.94",
+ "version": "0.1.95",
"private": true,
"scripts": {
"dev": "vite",
diff --git a/package.json.bak b/package.json.bak
index 6ff2945..af6d380 100644
--- a/package.json.bak
+++ b/package.json.bak
@@ -1,6 +1,6 @@
{
"name": "keanuapp-weblite",
- "version": "0.1.93",
+ "version": "0.1.94",
"private": true,
"scripts": {
"dev": "vite",
From d5917dcdb55a5d140a4364935dea65a774cd7128 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E0=AE=A4=E0=AE=AE=E0=AE=BF=E0=AE=B4=E0=AF=8D=E0=AE=A8?=
=?UTF-8?q?=E0=AF=87=E0=AE=B0=E0=AE=AE=E0=AF=8D?=
Date: Mon, 29 Dec 2025 18:15:43 +0100
Subject: [PATCH 09/17] Added translation using Weblate (Tamil)
---
src/assets/translations/ta.json | 1 +
1 file changed, 1 insertion(+)
create mode 100644 src/assets/translations/ta.json
diff --git a/src/assets/translations/ta.json b/src/assets/translations/ta.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/src/assets/translations/ta.json
@@ -0,0 +1 @@
+{}
From 2a7927db72db88c8eb913b71476633ade13dec2d Mon Sep 17 00:00:00 2001
From: Weblate Translation Memory
Date: Mon, 29 Dec 2025 21:31:41 +0100
Subject: [PATCH 10/17] Translated using Weblate (Persian)
Currently translated at 85.2% (404 of 474 strings)
Translation: Guardian Project/Keanu Weblite
Translate-URL: https://hosted.weblate.org/projects/guardianproject/keanu-weblite/fa/
---
src/assets/translations/fa.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/assets/translations/fa.json b/src/assets/translations/fa.json
index cc66779..d9cd6cc 100644
--- a/src/assets/translations/fa.json
+++ b/src/assets/translations/fa.json
@@ -465,7 +465,8 @@
"screenshot_probably": "تصویر شبیه به نماگرفت است.",
"generated_with_ai": "ساخته شده با هوشی.",
"modified": "دستکاری شده.",
- "older_than_n_months": "قدیمیتر از {n} ماه."
+ "older_than_n_months": "قدیمیتر از {n} ماه.",
+ "download_image": "بارگیری تصاویر"
},
"delete_post": {
"confirm_text_desc": "این عمل قابل بازگشت نیست."
From 9304912ce14b6fccb2ad98ae21bd0a03d07adc7e Mon Sep 17 00:00:00 2001
From: Weblate Translation Memory
Date: Mon, 29 Dec 2025 21:32:08 +0100
Subject: [PATCH 11/17] Translated using Weblate (Persian)
Currently translated at 85.2% (404 of 474 strings)
Translation: Guardian Project/Keanu Weblite
Translate-URL: https://hosted.weblate.org/projects/guardianproject/keanu-weblite/fa/
---
src/assets/translations/fa.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/assets/translations/fa.json b/src/assets/translations/fa.json
index d9cd6cc..b3cc102 100644
--- a/src/assets/translations/fa.json
+++ b/src/assets/translations/fa.json
@@ -469,6 +469,7 @@
"download_image": "بارگیری تصاویر"
},
"delete_post": {
- "confirm_text_desc": "این عمل قابل بازگشت نیست."
+ "confirm_text_desc": "این عمل قابل بازگشت نیست.",
+ "confirm_text": "آیا مطمئن هستید که می خواهید این تصویر را حذف کنید؟"
}
}
From 0b61def3529b7bcc7630d3077bdf2b4eb7c5d433 Mon Sep 17 00:00:00 2001
From: Danial Behzadi
Date: Mon, 29 Dec 2025 21:32:47 +0100
Subject: [PATCH 12/17] Translated using Weblate (Persian)
Currently translated at 85.2% (404 of 474 strings)
Translation: Guardian Project/Keanu Weblite
Translate-URL: https://hosted.weblate.org/projects/guardianproject/keanu-weblite/fa/
---
src/assets/translations/fa.json | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/assets/translations/fa.json b/src/assets/translations/fa.json
index b3cc102..ce547ec 100644
--- a/src/assets/translations/fa.json
+++ b/src/assets/translations/fa.json
@@ -161,7 +161,9 @@
"learn_more": "بیشتر بدانید",
"compressed": "فشرده",
"cc_source": "منبع",
- "cc_location": "مکان"
+ "cc_location": "مکان",
+ "captured_screenshot": "نماگرفت. ",
+ "screenshot": "نماگرفت. "
},
"project": {
"name": "تجمع",
@@ -466,10 +468,10 @@
"generated_with_ai": "ساخته شده با هوشی.",
"modified": "دستکاری شده.",
"older_than_n_months": "قدیمیتر از {n} ماه.",
- "download_image": "بارگیری تصاویر"
+ "download_image": "بارگیری تصویر"
},
"delete_post": {
"confirm_text_desc": "این عمل قابل بازگشت نیست.",
- "confirm_text": "آیا مطمئن هستید که می خواهید این تصویر را حذف کنید؟"
+ "confirm_text": "مطمئنید که میخواهید این پیام را حذف کنید؟"
}
}
From fe95d53cd132ac8a5f580c29dbbfca7fa901707d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=93=D0=BE=D1=80?=
=?UTF-8?q?=D0=BF=D0=B8=D0=BD=D1=96=D1=87?=
Date: Tue, 30 Dec 2025 09:29:37 +0100
Subject: [PATCH 13/17] Translated using Weblate (Ukrainian)
Currently translated at 100.0% (474 of 474 strings)
Translation: Guardian Project/Keanu Weblite
Translate-URL: https://hosted.weblate.org/projects/guardianproject/keanu-weblite/uk/
---
src/assets/translations/uk.json | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/assets/translations/uk.json b/src/assets/translations/uk.json
index 71d2e25..4241bdb 100644
--- a/src/assets/translations/uk.json
+++ b/src/assets/translations/uk.json
@@ -183,7 +183,7 @@
"options": "Опції"
},
"login": {
- "username": "Ім'я користувача (наприклад: Марта)",
+ "username": "Ім'я користувача (ex: '@user:server')",
"username_required": "Потрібне ім'я користувача",
"password_required": "Потрібен пароль",
"create_room": "Зареєструватися та створити кімнату",
@@ -261,7 +261,8 @@
"password_old": "Старий пароль",
"password_new": "Новий пароль",
"display_name": "Відображуване ім'я",
- "notification_label": "Сповіщення"
+ "notification_label": "Сповіщення",
+ "set_password_desc": "Convene не зберігає паролі та не може їх відновити. Ми рекомендуємо зберігати пароль у безпечному місці, перш ніж натискати кнопку «Зберегти»."
},
"profile_info_popup": {
"you_are": "Ви є",
@@ -318,7 +319,8 @@
"copy_credentials": "Скопіювати ім'я користувача та пароль",
"copied_credentials": "Скопійовано ім'я користувача та пароль",
"copied_credentials_value": "Ім'я користувача: {userId} \nПароль: {password}",
- "copy_credentials_desc": "Для відновлення доступу до чатів з нового пристрою або браузера потрібні ваші ім’я користувача та пароль. Рекомендуємо зберігати ці облікові дані в безпечному місці."
+ "copy_credentials_desc": "Для відновлення доступу до чатів з нового пристрою або браузера потрібні ваші ім’я користувача та пароль. Рекомендуємо зберігати ці облікові дані в безпечному місці.",
+ "copy_credentials_desc_for_real_user": "Ваше ім’я користувача (наприклад: '@user:server') та пароль потрібні для відновлення доступу до ваших чатів з нового пристрою або браузера."
},
"purge_room": {
"title": "Видалити кімнату?",
@@ -539,5 +541,10 @@
"delete_post": {
"confirm_text": "Ви впевнені, що хочете видалити це повідомлення?",
"confirm_text_desc": "Цю дію не можна скасувати."
+ },
+ "offline": {
+ "offline_title": "Виникли проблеми з підключенням?",
+ "offline_message": "Перенаправте на альтернативне посилання для приєднання до кімнати",
+ "offline_redirect": "Перенаправити мене"
}
}
From 7870a7c68fd8ca9522c238db8a6d23bbe0f8cc76 Mon Sep 17 00:00:00 2001
From: Weblate Translation Memory
Date: Mon, 29 Dec 2025 18:37:30 +0100
Subject: [PATCH 14/17] Translated using Weblate (Tamil)
Currently translated at 26.5% (126 of 474 strings)
Translation: Guardian Project/Keanu Weblite
Translate-URL: https://hosted.weblate.org/projects/guardianproject/keanu-weblite/ta/
---
src/assets/translations/ta.json | 189 +++++++++++++++++++++++++++++++-
1 file changed, 188 insertions(+), 1 deletion(-)
diff --git a/src/assets/translations/ta.json b/src/assets/translations/ta.json
index 0967ef4..b284582 100644
--- a/src/assets/translations/ta.json
+++ b/src/assets/translations/ta.json
@@ -1 +1,188 @@
-{}
+{
+ "global": {
+ "save": "சேமி",
+ "show_less": "குறைவாகக் காட்டு",
+ "show_more": "மேலும் காட்டு",
+ "time": {
+ "recently": "இப்போது",
+ "minutes": "1 மணித்துளி முன்பு | {n} நிமிடங்களுக்கு முன்பு",
+ "hours": "1 மணி நேரத்திற்கு முன்பு | {n} மணிநேரங்களுக்கு முன்பு",
+ "days": "1 நாள் முன்பு | {n} சில நாட்களுக்கு முன்பு"
+ },
+ "notify": "அறிவிக்கவும்",
+ "retry": "மீண்டும் முயற்சிக்கவும்"
+ },
+ "menu": {
+ "direct_chat": "நேரடி அரட்டை",
+ "reply": "பதில்",
+ "edit": "தொகு",
+ "delete": "அழி",
+ "download": "பதிவிறக்கம்",
+ "done": "முடிந்தது",
+ "cancel": "நீக்கு",
+ "send": "அனுப்பு",
+ "login": "புகுபதிவு",
+ "logout": "வெளியேற்றம்",
+ "undo": "செயல்தவிர்",
+ "join": "சேர",
+ "ignore": "புறக்கணிக்கவும்",
+ "user_make_admin": "நிர்வாகி",
+ "user_make_moderator": "மதிப்பீட்டாளரை உருவாக்குங்கள்",
+ "upgrade": "மேம்படுத்தல்"
+ },
+ "message": {
+ "someone": "யாரோ",
+ "edited": "(திருத்தப்பட்டது)",
+ "room_joinrule_public": "பொது",
+ "reply_image": "படம்",
+ "reply_video": "ஒளிதோற்றம்",
+ "reply_poll": "வாக்கெடுப்பு",
+ "file": "கோப்பு",
+ "files": "கோப்புகள்",
+ "images": "படங்கள்",
+ "download_media": "மீடியாவைப் பதிவிறக்கவும்",
+ "download_all": "அனைத்தையும் பதிவிறக்கு",
+ "room_upgraded_link": "இங்கே"
+ },
+ "room": {
+ "leave": "விடுப்பு",
+ "room_list_invites": "அழைப்புகள்",
+ "room_list_rooms": "அறைகள்"
+ },
+ "room_welcome": {
+ "got_it": "கிடைத்தது",
+ "change": "மாற்றம்"
+ },
+ "new_room": {
+ "create": "உருவாக்கு",
+ "next": "அடுத்தது",
+ "options": "விருப்பங்கள்"
+ },
+ "device_list": {
+ "blocked": "தடுக்கப்பட்டது",
+ "verified": "சரிபார்க்கப்பட்டது",
+ "not_verified": "சரிபார்க்கப்படவில்லை"
+ },
+ "login": {
+ "title": "புகுபதிவு",
+ "password": "கடவுச்சொல்லை உள்ளிடவும்",
+ "login": "புகுபதிவு",
+ "invalid_message": "தவறான பயனர்பெயர் அல்லது கடவுச்சொல்",
+ "accept_terms": "ஏற்றுக்கொள்",
+ "resend_verification": "சரிபார்ப்பு மின்னஞ்சல் மீண்டும் அனுப்பவும்",
+ "token_not_valid": "தவறான கிள்ளாக்கு"
+ },
+ "getlink": {
+ "next": "அடுத்தது",
+ "continue": "தொடரவும்"
+ },
+ "create": {
+ "room_type_channel_name": "வாய்க்கால்",
+ "field_required": "இந்த புலம் தேவை"
+ },
+ "profile": {
+ "set_password": "கடவுச்சொல்லை அமைக்கவும்",
+ "change_password": "கடவுச்சொல்லை மாற்றவும்",
+ "select_language": "மொழி",
+ "password_new": "புதிய கடவுச்சொல்",
+ "display_name": "காட்சி பெயர்",
+ "notification_label": "அறிவிப்பு"
+ },
+ "profile_info_popup": {
+ "edit_profile": "தொகு சுயவிவரம்",
+ "logout": "வெளியேற்றம்"
+ },
+ "join": {
+ "user_name_label": "பயனர் பெயர்",
+ "remember_me": "என்னை நினைவில் கொள்ளுங்கள்",
+ "join": "அறையில் சேரவும்",
+ "knock": "தட்டவும்",
+ "enter_room": "அறையை உள்ளிடவும்"
+ },
+ "invite": {
+ "title": "நண்பர்களைச் சேர்க்கவும்",
+ "done": "முடிந்தது"
+ },
+ "leave": {
+ "go_back": "திரும்பிச் செல்லுங்கள்",
+ "leave": "விடுப்பு"
+ },
+ "delete_post": {
+ "confirm_text_desc": "இந்த செயலை செயல்தவிர்க்க முடியாது."
+ },
+ "purge_room": {
+ "button": "அழி"
+ },
+ "room_info": {
+ "copy_link": "இணைப்பை நகலெடு",
+ "members": "உறுப்பினர்கள்",
+ "accept_knock": "ஏற்றுக்கொள்",
+ "reject_knock": "மறுக்கவும்",
+ "hide_all": "மறை",
+ "leave_room": "விடுப்பு",
+ "user_creator": "உருவாக்கியவர்",
+ "user_admin": "நிர்வாகி",
+ "user_moderator": "மதிப்பீட்டாளர்",
+ "moderation": "மிதமான",
+ "room_type_default": "இயல்புநிலை",
+ "read_only_room": "படிக்கவும்",
+ "message_retention_2_week": "2 வாரங்கள்",
+ "message_retention_1_week": "1 வாரம்",
+ "message_retention_1_day": "1 நாள்",
+ "message_retention_8_hours": "8 மணி நேரம்",
+ "message_retention_1_hour": "1 மணி நேரம்",
+ "report": "அறிக்கை",
+ "report_reason": "காரணம்"
+ },
+ "room_info_sheet": {
+ "view_details": "விபரங்களை பார்"
+ },
+ "voice_recorder": {
+ "not_supported_title": "ஆதரிக்கப்படவில்லை"
+ },
+ "power_level": {
+ "default": "இயல்புநிலை"
+ },
+ "fallbacks": {
+ "audio_file": "ஆடியோ கோப்பு",
+ "video_file": "வீடியோ கோப்பு",
+ "download_name": "பதிவிறக்கம்"
+ },
+ "poll_create": {
+ "create": "வெளியிடுங்கள்",
+ "answer_label_n": "பதில்",
+ "create_poll_menu_option": "வாக்கெடுப்பை உருவாக்கவும்",
+ "view_results": "முடிவுகளைக் காண்க",
+ "poll_submit": "சமர்ப்பிக்கவும்"
+ },
+ "notification": {
+ "dialog": {
+ "enable": "இயக்கு"
+ }
+ },
+ "emoji": {
+ "categories": {
+ "activity": "செய்கைப்பாடு",
+ "flags": "கொடிகள்",
+ "objects": "பொருள்கள்",
+ "nature": "இயற்கை",
+ "symbols": "குறியிடுகள்",
+ "places": "இடங்கள்"
+ }
+ },
+ "file_mode": {
+ "sending_progress": "அனுப்புகிறது ...",
+ "sending": "அனுப்புகிறது",
+ "close": "மூடு",
+ "files": "கோப்புகள்",
+ "quality": "தகுதி",
+ "original": "அசல்",
+ "learn_more": "மேலும் அறிய",
+ "cc_source": "மூலம்",
+ "cc_location": "இடம்"
+ },
+ "cc": {
+ "details": "விவரங்கள்",
+ "download_image": "படத்தைப் பதிவிறக்கவும்"
+ }
+}
From 220bc2038bd07495dea1dec785ea5b8166204386 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aindri=C3=BA=20Mac=20Giolla=20Eoin?=
Date: Fri, 2 Jan 2026 16:13:18 +0100
Subject: [PATCH 15/17] Translated using Weblate (Irish)
Currently translated at 100.0% (474 of 474 strings)
Translation: Guardian Project/Keanu Weblite
Translate-URL: https://hosted.weblate.org/projects/guardianproject/keanu-weblite/ga/
---
src/assets/translations/ga.json | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/assets/translations/ga.json b/src/assets/translations/ga.json
index 73bd72b..4c2e92c 100644
--- a/src/assets/translations/ga.json
+++ b/src/assets/translations/ga.json
@@ -108,7 +108,7 @@
"registration_token": "Cuir isteach comhartha clárúcháin",
"send_token": "Seol comhartha",
"token_not_valid": "Comhartha míbhailí",
- "username": "Ainm úsáideora (m.sh.: marta)"
+ "username": "Ainm úsáideora (mar shampla: '@user:server')"
},
"join": {
"you_have_been_banned": "Cuireadh cosc ort ón seomra seo.",
@@ -438,7 +438,8 @@
"display_name": "Ainm taispeána",
"display_name_required": "Tá ainm taispeána ag teastáil",
"notification_label": "Fógra",
- "my_rooms": "Mo sheomraí"
+ "my_rooms": "Mo sheomraí",
+ "set_password_desc": "Ní stórálann Convene pasfhocail agus ní féidir leo iad a aisghabháil. Molaimid do phasfhocal a stóráil in áit shábháilte sula gcliceálann tú ar 'sábháil'."
},
"profile_info_popup": {
"you_are": "Tá tú",
@@ -484,7 +485,8 @@
"copy_credentials": "Cóipeáil Ainm Úsáideora & Pasfhocal",
"copied_credentials": "Ainm úsáideora & Pasfhocal cóipeáilte",
"copied_credentials_value": "Ainm úsáideora: {userId} \nPasfhocal: {password}",
- "copy_credentials_desc": "Tá d’ainm úsáideora agus do phasfhocal ag teastáil chun rochtain a fháil ar do chomhráite arís ó ghléas nó brabhsálaí nua. Molaimid na dintiúir seo a stóráil in áit shábháilte."
+ "copy_credentials_desc": "Tá d’ainm úsáideora agus do phasfhocal ag teastáil chun rochtain a fháil ar do chomhráite arís ó ghléas nó brabhsálaí nua. Molaimid na dintiúir seo a stóráil in áit shábháilte.",
+ "copy_credentials_desc_for_real_user": "Tá d’ainm úsáideora (mar shampla: '@user:server') agus do phasfhocal ag teastáil chun rochtain a fháil ar do chomhráite arís ó ghléas nó brabhsálaí nua."
},
"fallbacks": {
"audio_file": "Comhad fuaime",
@@ -539,5 +541,10 @@
"delete_post": {
"confirm_text": "An bhfuil tú cinnte gur mian leat an teachtaireacht seo a scriosadh?",
"confirm_text_desc": "Ní féidir an gníomh seo a chealú."
+ },
+ "offline": {
+ "offline_title": "An bhfuil deacracht agat ceangal?",
+ "offline_message": "Atreoraigh chuig nasc malartach chun páirt a ghlacadh sa seomra",
+ "offline_redirect": "Athsheol mé"
}
}
From b1b793edc42b73d84b8f215daec84713f5600416 Mon Sep 17 00:00:00 2001
From: N-Pex
Date: Tue, 13 Jan 2026 11:38:14 +0100
Subject: [PATCH 16/17] Fix migration for servers without refresh tokens
---
src/services/matrix.service.js | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/services/matrix.service.js b/src/services/matrix.service.js
index 5f21070..993d003 100644
--- a/src/services/matrix.service.js
+++ b/src/services/matrix.service.js
@@ -149,8 +149,13 @@ export default {
baseUrl: baseUrl,
idBaseUrl: this.$config.identityServer,
});
+ user.access_token = undefined;
user.device_id = tempMatrixClient.getSessionId();
- return this.refreshAccessToken(tempMatrixClient, user.refresh_token);
+ if (user.refresh_token) {
+ return this.refreshAccessToken(tempMatrixClient, user.refresh_token);
+ } else {
+ return Promise.resolve(true);
+ }
}).then(() => {
console.log("Refresh done, normal login");
return this.login(user, undefined, false, true);
From 6cd8ce0a603d5330cf8338028c301175ed67ea20 Mon Sep 17 00:00:00 2001
From: N-Pex
Date: Tue, 13 Jan 2026 11:39:37 +0100
Subject: [PATCH 17/17] Build 96
---
package.json | 2 +-
package.json.bak | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index 69f886f..0f1636d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "keanuapp-weblite",
- "version": "0.1.95",
+ "version": "0.1.96",
"private": true,
"scripts": {
"dev": "vite",
diff --git a/package.json.bak b/package.json.bak
index af6d380..69f886f 100644
--- a/package.json.bak
+++ b/package.json.bak
@@ -1,6 +1,6 @@
{
"name": "keanuapp-weblite",
- "version": "0.1.94",
+ "version": "0.1.95",
"private": true,
"scripts": {
"dev": "vite",