mirror of
https://github.com/RetroShare/RSNewWebUI.git
synced 2026-09-12 19:50:04 +05:00
Merge pull request #101 from jolavillette/ImproveWebui
improve the webui and make it actually usable on a telephone
This commit is contained in:
commit
22b2939858
@ -8,35 +8,74 @@ function loadLobbyDetails(id, apply) {
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsChats/getChatLobbyInfo',
|
||||
{
|
||||
id,
|
||||
id: { xstr64: id },
|
||||
},
|
||||
(detail) => {
|
||||
if (detail.retval) {
|
||||
(detail, success) => {
|
||||
if (success && detail.retval) {
|
||||
detail.info.chatType = 3; // LOBBY
|
||||
apply(detail.info);
|
||||
} else {
|
||||
apply(null);
|
||||
}
|
||||
},
|
||||
true,
|
||||
{},
|
||||
undefined,
|
||||
// Custom serializer NOTE:
|
||||
// Since id represents 64-bit int(see deserializer note below)
|
||||
// Instead of using JSON.stringify, this function directly
|
||||
// creates a json string manually.
|
||||
() => '{"id":' + id + '}'
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
function loadDistantChatDetails(pid, apply) {
|
||||
// pid is DistantChatPeerId (uint32)
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsChats/getDistantChatStatus',
|
||||
{
|
||||
pid: pid,
|
||||
},
|
||||
(detail, success) => {
|
||||
if (success && detail.retval) {
|
||||
// Map to lobby-like structure for UI compatibility
|
||||
const info = detail.info;
|
||||
info.chatType = 2; // DISTANT (matches TYPE_PRIVATE_DISTANT in rschats.h)
|
||||
info.lobby_name = rs.userList.username(info.to_id) || 'Distant Chat ' + pid;
|
||||
info.lobby_topic = 'Private Encrypted Chat';
|
||||
info.gxs_id = info.own_id;
|
||||
info.lobby_id = pid; // Distant IDs are 128-bit hex strings, NO xstr64 wrapper
|
||||
apply(info);
|
||||
} else {
|
||||
apply(null);
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
function sortLobbies(lobbies) {
|
||||
if (lobbies !== undefined) {
|
||||
if (lobbies !== undefined && lobbies !== null) {
|
||||
const list = [...lobbies];
|
||||
list.sort((a, b) => a.lobby_name.localeCompare(b.lobby_name));
|
||||
return list;
|
||||
}
|
||||
// return lobbies; // fallback on reload page in browser, keep undefiend
|
||||
return []; // return empty array instead of undefined
|
||||
}
|
||||
|
||||
// ***************************** models ***********************************
|
||||
|
||||
const MobileState = {
|
||||
showLobbies: false,
|
||||
showUsers: false,
|
||||
toggleLobbies() {
|
||||
this.showLobbies = !this.showLobbies;
|
||||
this.showUsers = false;
|
||||
},
|
||||
toggleUsers() {
|
||||
this.showUsers = !this.showUsers;
|
||||
this.showLobbies = false;
|
||||
},
|
||||
closeAll() {
|
||||
this.showLobbies = false;
|
||||
this.showUsers = false;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
const ChatRoomsModel = {
|
||||
allRooms: [],
|
||||
knownSubscrIds: [], // to exclude subscribed from public rooms (subscribedRooms filled to late)
|
||||
@ -47,40 +86,73 @@ const ChatRoomsModel = {
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsChats/getListOfNearbyChatLobbies',
|
||||
{},
|
||||
(data) => (ChatRoomsModel.allRooms = sortLobbies(data.public_lobbies))
|
||||
(data) => {
|
||||
if (data && data.public_lobbies) {
|
||||
// Deduplicate by ID to avoid double display if backend returns redundant info
|
||||
const seen = new Set();
|
||||
const uniqueLobbies = data.public_lobbies.filter((lobby) => {
|
||||
const id = rs.idToHex(lobby.lobby_id);
|
||||
if (seen.has(id)) return false;
|
||||
seen.add(id);
|
||||
return true;
|
||||
});
|
||||
ChatRoomsModel.allRooms = sortLobbies(uniqueLobbies);
|
||||
} else {
|
||||
// No public lobbies
|
||||
ChatRoomsModel.allRooms = [];
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
loadSubscribedRooms(after = null) {
|
||||
// ChatRoomsModel.subscribedRooms = {};
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsChats/getChatLobbyList',
|
||||
{},
|
||||
// JS uses double precision numbers of 64 bit. It is equivalent
|
||||
// to 53 bits of precision. All large precision ints will
|
||||
// get truncated to an approximation.
|
||||
// This API uses Cpp-style 64 bits for `id`.
|
||||
// So we use the string-value 'xstr64' instead
|
||||
(data) => {
|
||||
const ids = data.cl_list.map((lid) => lid.xstr64);
|
||||
ChatRoomsModel.knownSubscrIds = ids;
|
||||
const rooms = {};
|
||||
ids.map((id) =>
|
||||
loadLobbyDetails(id, (info) => {
|
||||
rooms[id] = info;
|
||||
if (Object.keys(rooms).length === ids.length) {
|
||||
// apply rooms to subscribedRooms only after reading all room-details, so sorting all or nothing
|
||||
ChatRoomsModel.subscribedRooms = rooms;
|
||||
if (data && data.cl_list) {
|
||||
// Robust deduplication of IDs
|
||||
const ids = [...new Set(data.cl_list.map((lid) => rs.idToHex(lid)))];
|
||||
ChatRoomsModel.knownSubscrIds = ids;
|
||||
|
||||
// Remove stale entries that are no longer in the subscribed list
|
||||
Object.keys(ChatRoomsModel.subscribedRooms).forEach((id) => {
|
||||
if (!ids.includes(id)) {
|
||||
delete ChatRoomsModel.subscribedRooms[id];
|
||||
}
|
||||
})
|
||||
);
|
||||
if (after != null) {
|
||||
after();
|
||||
});
|
||||
|
||||
if (ids.length === 0) {
|
||||
ChatRoomsModel.loadPublicRooms();
|
||||
if (after != null) after();
|
||||
m.redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
ids.forEach((id) =>
|
||||
loadLobbyDetails(id, (info) => {
|
||||
if (info) {
|
||||
ChatRoomsModel.subscribedRooms[id] = info;
|
||||
}
|
||||
count++;
|
||||
if (count === ids.length) {
|
||||
ChatRoomsModel.loadPublicRooms(); // Load public rooms after we know all subscribed IDs
|
||||
if (after != null) {
|
||||
after();
|
||||
}
|
||||
m.redraw();
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// No subscribed lobbies
|
||||
ChatRoomsModel.loadPublicRooms();
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
subscribed(info) {
|
||||
return this.knownSubscrIds.includes(info.lobby_id.xstr64);
|
||||
return this.knownSubscrIds.includes(rs.idToHex(info.lobby_id));
|
||||
},
|
||||
};
|
||||
|
||||
@ -90,28 +162,42 @@ const ChatRoomsModel = {
|
||||
* msg: Message to Display
|
||||
*/
|
||||
const Message = () => {
|
||||
let msg = null; // message to display
|
||||
let text = ''; // extracted text to display
|
||||
let datetime = ''; // date time to display
|
||||
let username = ''; // username to display (later may be linked)
|
||||
return {
|
||||
oninit: (vnode) => {
|
||||
console.info('chat Message', vnode);
|
||||
msg = vnode.attrs;
|
||||
datetime = new Date(msg.sendTime * 1000).toLocaleTimeString();
|
||||
username = rs.userList.username(msg.lobby_peer_gxs_id);
|
||||
text = msg.msg
|
||||
view: (vnode) => {
|
||||
const msg = vnode.attrs;
|
||||
const datetime = new Date(msg.sendTime * 1000).toLocaleTimeString();
|
||||
// Handle both HistoryMsg (peerId) and ChatMessage (lobby_peer_gxs_id)
|
||||
const rawGxsId = msg.lobby_peer_gxs_id || msg.peerId;
|
||||
let gxsId = rs.idToHex(rawGxsId);
|
||||
|
||||
// Fallback for 1-to-1 chats where sender ID might be missing (zeros)
|
||||
const isZero = (id) => !id || id === '00000000000000000000000000000000';
|
||||
if (isZero(gxsId)) {
|
||||
const lobby = ChatLobbyModel.currentLobby;
|
||||
// Types 1 (Private), 2 (Distant) are "private" conversations here
|
||||
if (lobby && (lobby.chatType === 1 || lobby.chatType === 2)) {
|
||||
gxsId = msg.incoming ? rs.idToHex(lobby.to_id || lobby.peer_id || lobby.distant_chat_id) : rs.idToHex(lobby.own_id || lobby.gxs_id);
|
||||
}
|
||||
}
|
||||
|
||||
let username = rs.userList.username(gxsId) || msg.peerName || '???';
|
||||
// If we only have the hex ID, try to fallback to the peerName from the message
|
||||
if (username === gxsId && msg.peerName) {
|
||||
username = msg.peerName;
|
||||
}
|
||||
if (username === gxsId && gxsId && gxsId.length > 12) {
|
||||
username = gxsId.substring(0, 8) + '...';
|
||||
}
|
||||
const text = (msg.msg || msg.message || '')
|
||||
.replaceAll('<br/>', '\n')
|
||||
.replace(new RegExp('<style[^<]*</style>|<[^>]*>', 'gm'), '');
|
||||
console.info('chat Text', text);
|
||||
},
|
||||
view: () =>
|
||||
m(
|
||||
return m(
|
||||
'.message',
|
||||
m('span.datetime', datetime),
|
||||
m('span.username', username),
|
||||
m('span.messagetext', text)
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -123,86 +209,228 @@ const ChatLobbyModel = {
|
||||
isSubscribed: false,
|
||||
messages: [],
|
||||
users: [],
|
||||
setupAction: (lobbyId, nick) => {},
|
||||
messageKeys: new Set(),
|
||||
lastLobbyId: null,
|
||||
|
||||
// Helper to generate a unique key for deduplication
|
||||
getMessageKey(msg) {
|
||||
if (msg.msgId && msg.msgId !== 0) return 'id_' + msg.msgId;
|
||||
// Fallback for live messages or history without IDs
|
||||
const text = msg.msg || msg.message || '';
|
||||
return 't_' + msg.sendTime + '_' + text.substring(0, 32);
|
||||
},
|
||||
|
||||
addMessages(newMsgs, scroll = false) {
|
||||
let added = false;
|
||||
newMsgs.forEach((msg) => {
|
||||
const key = this.getMessageKey(msg);
|
||||
if (!this.messageKeys.has(key)) {
|
||||
// Near-duplicate check for messages without IDs (live events vs optimistic echo)
|
||||
const text = msg.msg || msg.message || '';
|
||||
const isNearDuplicate = this.messages.some((existingMsg) => {
|
||||
const eAttrs = existingMsg.attrs;
|
||||
const eText = eAttrs.msg || eAttrs.message || '';
|
||||
return (
|
||||
eText === text &&
|
||||
Math.abs(eAttrs.sendTime - msg.sendTime) < 5 // 5 seconds window
|
||||
);
|
||||
});
|
||||
|
||||
if (!isNearDuplicate) {
|
||||
this.messageKeys.add(key);
|
||||
this.messages.push(m(Message, msg));
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (added) {
|
||||
this.messages.sort((a, b) => a.attrs.sendTime - b.attrs.sendTime);
|
||||
m.redraw();
|
||||
if (scroll) {
|
||||
setTimeout(() => {
|
||||
const element = document.querySelector('.messages');
|
||||
if (element) {
|
||||
element.scrollTop = element.scrollHeight;
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
loadHistory(id, type) {
|
||||
const chatPeerId = {
|
||||
broadcast_status_peer_id: '00000000000000000000000000000000',
|
||||
type: type,
|
||||
peer_id: '00000000000000000000000000000000',
|
||||
distant_chat_id: '00000000000000000000000000000000',
|
||||
lobby_id: { xstr64: '0' },
|
||||
};
|
||||
|
||||
if (type === 3) chatPeerId.lobby_id.xstr64 = id;
|
||||
else if (type === 2) chatPeerId.distant_chat_id = id;
|
||||
else if (type === 1) chatPeerId.peer_id = id;
|
||||
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsHistory/getMessages',
|
||||
{
|
||||
chatPeerId: chatPeerId,
|
||||
loadCount: 20,
|
||||
},
|
||||
(data, success) => {
|
||||
if (success && data.msgs) {
|
||||
this.addMessages(data.msgs);
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
setupAction: (lobbyId, nick) => { },
|
||||
setIdentity(lobbyId, nick) {
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsChats/setIdentityForChatLobby',
|
||||
{},
|
||||
() => m.route.set('/chat/:lobby_id', { lobbyId }),
|
||||
true,
|
||||
{},
|
||||
JSON.parse,
|
||||
() => '{"lobby_id":' + lobbyId + ',"nick":"' + nick + '"}'
|
||||
{
|
||||
lobby_id: { xstr64: lobbyId },
|
||||
nick: nick,
|
||||
},
|
||||
() => m.route.set('/chat/:lobby', { lobby: lobbyId }),
|
||||
true
|
||||
);
|
||||
},
|
||||
enterPublicLobby(lobbyId, nick) {
|
||||
console.info('joinVisibleChatLobby', nick, '@', lobbyId);
|
||||
// Set lobby nickname
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsChats/joinVisibleChatLobby',
|
||||
{},
|
||||
{
|
||||
lobby_id: { xstr64: lobbyId },
|
||||
own_id: nick,
|
||||
},
|
||||
() => {
|
||||
loadLobbyDetails(lobbyId, (info) => {
|
||||
ChatRoomsModel.subscribedRooms[lobbyId] = info;
|
||||
ChatRoomsModel.loadSubscribedRooms(() => {
|
||||
m.route.set('/chat/:lobby', { lobby: info.lobby_id.xstr64 });
|
||||
m.route.set('/chat/:lobby', { lobby: rs.idToHex(info.lobby_id) });
|
||||
});
|
||||
});
|
||||
},
|
||||
true,
|
||||
{},
|
||||
JSON.parse,
|
||||
() => '{"lobby_id":' + lobbyId + ',"own_id":"' + nick + '"}'
|
||||
true
|
||||
);
|
||||
},
|
||||
unsubscribeChatLobby(lobbyId, follow) {
|
||||
console.info('unsubscribe lobby', lobbyId);
|
||||
// Unsubscribe
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsChats/unsubscribeChatLobby',
|
||||
{},
|
||||
() => ChatRoomsModel.loadSubscribedRooms(follow),
|
||||
true,
|
||||
{},
|
||||
JSON.parse,
|
||||
() => '{"lobby_id":' + lobbyId + '}'
|
||||
{
|
||||
lobby_id: { xstr64: lobbyId },
|
||||
},
|
||||
(data, success) => {
|
||||
if (success) {
|
||||
ChatRoomsModel.loadSubscribedRooms(follow);
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
chatId(action) {
|
||||
return { type: 3, lobby_id: { xstr64: m.route.param('lobby') } };
|
||||
chatId() {
|
||||
const type = (this.currentLobby && this.currentLobby.chatType) || 3;
|
||||
const id = this.lastLobbyId || m.route.param('lobby');
|
||||
const cid = {
|
||||
broadcast_status_peer_id: '00000000000000000000000000000000',
|
||||
type: type,
|
||||
peer_id: '00000000000000000000000000000000',
|
||||
distant_chat_id: '00000000000000000000000000000000',
|
||||
lobby_id: { xstr64: '0' },
|
||||
};
|
||||
if (type === 3) cid.lobby_id.xstr64 = id;
|
||||
else if (type === 2) cid.distant_chat_id = id;
|
||||
else if (type === 1) cid.peer_id = id;
|
||||
return cid;
|
||||
},
|
||||
loadLobby(currentlobbyid) {
|
||||
loadLobbyDetails(currentlobbyid, (detail) => {
|
||||
this.lastLobbyId = currentlobbyid;
|
||||
|
||||
const finishLoad = (detail) => {
|
||||
this.setupAction = this.setIdentity;
|
||||
this.currentLobby = detail;
|
||||
this.isSubscribed = true;
|
||||
this.lobby_user = rs.userList.username(detail.gxs_id) || '???';
|
||||
const lobbyid = currentlobbyid;
|
||||
// apply existing messages to current lobby view
|
||||
rs.events[15].chatMessages(
|
||||
this.chatId(),
|
||||
rs.events[15],
|
||||
(l) => (this.messages = l.map((msg) => m(Message, msg)))
|
||||
);
|
||||
// register for chatEvents for future messages
|
||||
|
||||
// Reset local state for this lobby
|
||||
this.messages = [];
|
||||
this.messageKeys.clear();
|
||||
|
||||
// Load history first
|
||||
this.loadHistory(currentlobbyid, detail.chatType);
|
||||
|
||||
// Apply existing messages from live cache
|
||||
const cid = this.chatId();
|
||||
rs.events[15].chatMessages(cid, rs.events[15], (l) => {
|
||||
this.addMessages(l);
|
||||
});
|
||||
|
||||
// Register for chatEvents for future messages
|
||||
rs.events[15].notify = (chatMessage) => {
|
||||
if (chatMessage.chat_id.type === 3 && chatMessage.chat_id.lobby_id.xstr64 === lobbyid) {
|
||||
this.messages.push(m(Message, chatMessage));
|
||||
m.redraw();
|
||||
// DEBUG: Log incoming message structure
|
||||
console.log('[RS-DEBUG] Incoming Chat Message:', JSON.stringify(chatMessage, null, 2));
|
||||
|
||||
const msgCid = chatMessage.chat_id;
|
||||
let msgId;
|
||||
|
||||
if (msgCid.type === 3) {
|
||||
msgId = rs.idToHex(msgCid.lobby_id);
|
||||
} else if (msgCid.type === 2) {
|
||||
// For Distant Chat, the ID is the distant_chat_id
|
||||
msgId = rs.idToHex(msgCid.distant_chat_id);
|
||||
} else if (msgCid.type === 1) {
|
||||
// For Private Chat, the ID is the peer_id
|
||||
msgId = rs.idToHex(msgCid.peer_id);
|
||||
} else {
|
||||
// Fallback
|
||||
msgId = rs.idToHex(msgCid);
|
||||
}
|
||||
|
||||
console.log('[RS-DEBUG] Resolved Msg ID:', msgId, 'Current Lobby ID:', currentlobbyid, 'Match:', msgId === currentlobbyid);
|
||||
|
||||
if (msgId === currentlobbyid) {
|
||||
this.addMessages([chatMessage]);
|
||||
}
|
||||
};
|
||||
// lookup for chat-user names (only snapshot, we don't get notified about changes of participants)
|
||||
const names = detail.gxs_ids.reduce((a, u) => a.concat(rs.userList.username(u.key)), []);
|
||||
names.sort((a, b) => a.localeCompare(b));
|
||||
this.users = [];
|
||||
names.forEach((name) => (this.users = this.users.concat([m('.user', name)])));
|
||||
return this.users;
|
||||
|
||||
// Lookup for chat-user names (Only for lobbies for now)
|
||||
// Lookup for chat-user names
|
||||
if (detail.gxs_ids) {
|
||||
let names = [];
|
||||
if (Array.isArray(detail.gxs_ids)) {
|
||||
names = detail.gxs_ids.reduce((a, u) => a.concat(rs.userList.username(u.key)), []);
|
||||
} else if (typeof detail.gxs_ids === 'object') {
|
||||
names = Object.keys(detail.gxs_ids).map(key => rs.userList.username(key));
|
||||
}
|
||||
names.sort((a, b) => a.localeCompare(b));
|
||||
this.users = [];
|
||||
names.forEach((name) => (this.users = this.users.concat([m('.user', name)])));
|
||||
} else {
|
||||
this.users = [m('.user', detail.lobby_name)];
|
||||
}
|
||||
m.redraw();
|
||||
};
|
||||
|
||||
loadLobbyDetails(currentlobbyid, (detail) => {
|
||||
if (detail) {
|
||||
finishLoad(detail);
|
||||
} else {
|
||||
// Fallback to Distant Chat
|
||||
loadDistantChatDetails(currentlobbyid, (dDetail) => {
|
||||
if (dDetail) {
|
||||
finishLoad(dDetail);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
loadPublicLobby(currentlobbyid) {
|
||||
console.info('loadPublicLobby ChatRoomsModel:', ChatRoomsModel);
|
||||
this.setupAction = this.enterPublicLobby;
|
||||
this.isSubscribed = false;
|
||||
ChatRoomsModel.allRooms.forEach((it) => {
|
||||
if (it.lobby_id.xstr64 === currentlobbyid) {
|
||||
if (rs.idToHex(it.lobby_id) === currentlobbyid) {
|
||||
this.currentLobby = it;
|
||||
this.lobby_user = '???';
|
||||
this.lobbyid = currentlobbyid;
|
||||
@ -211,51 +439,47 @@ const ChatLobbyModel = {
|
||||
this.users = [];
|
||||
},
|
||||
sendMessage(msg, onsuccess) {
|
||||
const cid = this.chatId();
|
||||
// Optimistic echo for immediate feedback
|
||||
const echoMsg = {
|
||||
chat_id: cid,
|
||||
msg: msg,
|
||||
sendTime: Math.floor(Date.now() / 1000),
|
||||
lobby_peer_gxs_id: this.currentLobby.gxs_id,
|
||||
};
|
||||
this.addMessages([echoMsg], true);
|
||||
|
||||
rs.rsJsonApiRequest(
|
||||
'/rsChats/sendChat',
|
||||
{},
|
||||
() => {
|
||||
// adding own message to log
|
||||
rs.events[15].handler(
|
||||
{
|
||||
mChatMessage: {
|
||||
chat_id: this.chatId(),
|
||||
msg,
|
||||
sendTime: new Date().getTime() / 1000,
|
||||
lobby_peer_gxs_id: this.currentLobby.gxs_id,
|
||||
},
|
||||
},
|
||||
rs.events[15]
|
||||
);
|
||||
onsuccess();
|
||||
{
|
||||
id: cid,
|
||||
msg: msg,
|
||||
},
|
||||
true,
|
||||
{},
|
||||
undefined,
|
||||
() =>
|
||||
'{"id":{"type": 3,"lobby_id":' +
|
||||
m.route.param('lobby') +
|
||||
'}, "msg":' +
|
||||
JSON.stringify(msg) +
|
||||
'}'
|
||||
(data, success) => {
|
||||
if (success) {
|
||||
onsuccess();
|
||||
} else {
|
||||
console.error('[RS] Failed to send chat message');
|
||||
onsuccess(); // Clear the input even on failure to avoid stuck 'sending...' state
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
selected(info, selName, defaultName) {
|
||||
const currid = (ChatLobbyModel.currentLobby.lobby_id || { xstr64: m.route.param('lobby') })
|
||||
.xstr64;
|
||||
return (info.lobby_id.xstr64 === currid ? selName : '') + defaultName;
|
||||
const currid = rs.idToHex(ChatLobbyModel.currentLobby.lobby_id || { xstr64: m.route.param('lobby') });
|
||||
return (rs.idToHex(info.lobby_id) === currid ? selName : '') + defaultName;
|
||||
},
|
||||
switchToEvent(info) {
|
||||
return () => {
|
||||
ChatLobbyModel.currentLobby = info;
|
||||
m.route.set('/chat/:lobby', { lobby: info.lobby_id.xstr64 });
|
||||
ChatLobbyModel.loadLobby(info.lobby_id.xstr64); // update
|
||||
m.route.set('/chat/:lobby', { lobby: rs.idToHex(info.lobby_id) });
|
||||
ChatLobbyModel.loadLobby(rs.idToHex(info.lobby_id)); // update
|
||||
};
|
||||
},
|
||||
setupEvent(info) {
|
||||
return () => {
|
||||
m.route.set('/chat/:lobby/setup', { lobby: info.lobby_id.xstr64 });
|
||||
ChatLobbyModel.loadPublicLobby(info.lobby_id.xstr64); // update
|
||||
m.route.set('/chat/:lobby/setup', { lobby: rs.idToHex(info.lobby_id) });
|
||||
ChatLobbyModel.loadPublicLobby(rs.idToHex(info.lobby_id)); // update
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -263,23 +487,13 @@ const ChatLobbyModel = {
|
||||
// ************************* views ****************************
|
||||
|
||||
const Lobby = () => {
|
||||
let info = {};
|
||||
let tagname = '';
|
||||
let onclick = (e) => {};
|
||||
let lobbytagname = '';
|
||||
return {
|
||||
oninit: (v) => {
|
||||
info = v.attrs.info;
|
||||
tagname = v.attrs.tagname;
|
||||
onclick = v.attrs.onclick || ((e) => {});
|
||||
lobbytagname = v.attrs.lobbytagname || 'mainname';
|
||||
},
|
||||
view: (v) => {
|
||||
view: (vnode) => {
|
||||
const { info, tagname, onclick, lobbytagname = 'mainname' } = vnode.attrs;
|
||||
return m(
|
||||
ChatLobbyModel.selected(info, '.selected-lobby', tagname),
|
||||
{
|
||||
key: info.lobby_id.xstr64,
|
||||
|
||||
key: rs.idToHex(info.lobby_id),
|
||||
onclick,
|
||||
},
|
||||
[
|
||||
@ -343,7 +557,7 @@ const PublicLeftLobbies = {
|
||||
return [
|
||||
m('h5.lefttitle', 'public:'),
|
||||
m(LobbyList, {
|
||||
rooms: Object.values(ChatRoomsModel.allRooms).filter(
|
||||
rooms: Object.values(ChatRoomsModel.allRooms || {}).filter(
|
||||
(info) => !ChatRoomsModel.subscribed(info)
|
||||
),
|
||||
tagname: '.leftlobby.public',
|
||||
@ -354,89 +568,158 @@ const PublicLeftLobbies = {
|
||||
},
|
||||
};
|
||||
|
||||
const PublicLobbies = () => {
|
||||
return m('.widget', [
|
||||
m('.widget__heading', m('h3', 'Public chat rooms')),
|
||||
m('.widget__body', [
|
||||
m(LobbyList, {
|
||||
rooms: ChatRoomsModel.allRooms.filter((info) => !ChatRoomsModel.subscribed(info)),
|
||||
tagname: '.lobby.public',
|
||||
onclick: ChatLobbyModel.setupEvent,
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
const PublicLobbies = {
|
||||
view() {
|
||||
return m('.widget', [
|
||||
m('.widget__heading', m('h3', 'Public chat rooms')),
|
||||
m('.widget__body', [
|
||||
m(LobbyList, {
|
||||
rooms: (ChatRoomsModel.allRooms || []).filter((info) => !ChatRoomsModel.subscribed(info)),
|
||||
tagname: '.lobby.public',
|
||||
onclick: ChatLobbyModel.setupEvent,
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
const LobbyName = () => {
|
||||
return m(
|
||||
'h3.lobbyName',
|
||||
m('.mobile-menu-icons', [
|
||||
m('i.fas.fa-bars', { onclick: () => MobileState.toggleLobbies() }),
|
||||
]),
|
||||
ChatLobbyModel.isSubscribed
|
||||
? [m('span.chatusername', ChatLobbyModel.lobby_user), m('span.chatatchar', '@')]
|
||||
: [],
|
||||
ChatLobbyModel.currentLobby.chatType === 2
|
||||
? m('i.fas.fa-circle', {
|
||||
style: {
|
||||
color:
|
||||
ChatLobbyModel.currentLobby.status === 2
|
||||
? '#2ecc71' // Green (Can Talk)
|
||||
: ChatLobbyModel.currentLobby.status === 1
|
||||
? '#f39c12' // Orange (Tunnel Down)
|
||||
: ChatLobbyModel.currentLobby.status === 3
|
||||
? '#e74c3c' // Red (Remotely Closed)
|
||||
: '#95a5a6', // Grey (Unknown)
|
||||
fontSize: '0.6em',
|
||||
marginRight: '10px',
|
||||
verticalAlign: 'middle',
|
||||
},
|
||||
title:
|
||||
ChatLobbyModel.currentLobby.status === 2
|
||||
? 'Tunnel Active (Can Talk)'
|
||||
: ChatLobbyModel.currentLobby.status === 1
|
||||
? 'Tunnel Down (Negotiating...)'
|
||||
: ChatLobbyModel.currentLobby.status === 3
|
||||
? 'Remotely Closed'
|
||||
: 'Status Unknown',
|
||||
})
|
||||
: [],
|
||||
m('span.chatlobbyname', ChatLobbyModel.currentLobby.lobby_name),
|
||||
m.route.param('subaction') !== 'setup'
|
||||
m('.mobile-menu-icons', [
|
||||
m('i.fas.fa-users', { onclick: () => MobileState.toggleUsers() }),
|
||||
]),
|
||||
m.route.param('subaction') !== 'setup' && ChatLobbyModel.currentLobby.chatType === 3
|
||||
? [
|
||||
m('i.fas.fa-cog.setupicon', {
|
||||
title: 'configure lobby',
|
||||
onclick: () =>
|
||||
m.route.set(
|
||||
'/chat/:lobby/:subaction',
|
||||
{
|
||||
lobby: m.route.param('lobby'),
|
||||
subaction: 'setup',
|
||||
},
|
||||
{ replace: true }
|
||||
),
|
||||
}),
|
||||
]
|
||||
m('i.fas.fa-cog.setupicon', {
|
||||
title: 'configure lobby',
|
||||
onclick: () =>
|
||||
m.route.set(
|
||||
'/chat/:lobby/:subaction',
|
||||
{
|
||||
lobby: m.route.param('lobby'),
|
||||
subaction: 'setup',
|
||||
},
|
||||
{ replace: true }
|
||||
),
|
||||
}),
|
||||
]
|
||||
: [],
|
||||
ChatLobbyModel.isSubscribed
|
||||
? [
|
||||
m('i.fas.fa-sign-out-alt.leaveicon', {
|
||||
title: 'leaving lobby',
|
||||
onclick: () =>
|
||||
ChatLobbyModel.unsubscribeChatLobby(m.route.param('lobby'), () => {
|
||||
m.route.set('/chat', null, { replace: true });
|
||||
}),
|
||||
}),
|
||||
]
|
||||
m('i.fas.fa-sign-out-alt.leaveicon', {
|
||||
title: 'leaving lobby',
|
||||
onclick: () =>
|
||||
ChatLobbyModel.unsubscribeChatLobby(m.route.param('lobby'), () => {
|
||||
m.route.set('/chat', null, { replace: true });
|
||||
}),
|
||||
}),
|
||||
]
|
||||
: []
|
||||
);
|
||||
};
|
||||
|
||||
// ***************************** Page Layouts ******************************
|
||||
|
||||
const Layout = () => {
|
||||
return {
|
||||
view: () => m('.node-panel', [m(SubscribedLobbies), PublicLobbies()]),
|
||||
};
|
||||
const Layout = {
|
||||
view: () => m('.node-panel.chat-panel.chat-hub', [m(SubscribedLobbies), m(PublicLobbies)]),
|
||||
};
|
||||
|
||||
const LayoutSingle = () => {
|
||||
const onResize = () => {
|
||||
const element = document.querySelector('.messages');
|
||||
if (element) element.scrollTop = element.scrollHeight;
|
||||
};
|
||||
return {
|
||||
oninit: () => ChatLobbyModel.loadLobby(m.route.param('lobby')),
|
||||
view: (vnode) =>
|
||||
m('.node-panel', [
|
||||
LobbyName(),
|
||||
m('.lobbies', m(SubscribedLeftLobbies), m(PublicLeftLobbies)),
|
||||
m('.messages', ChatLobbyModel.messages),
|
||||
m('.rightbar', ChatLobbyModel.users),
|
||||
m(
|
||||
'.chatMessage',
|
||||
{},
|
||||
m('textarea.chatMsg', {
|
||||
placeholder: 'enter new message and press return to send',
|
||||
onkeydown: (e) => {
|
||||
if (e.code === 'Enter') {
|
||||
const msg = e.target.value;
|
||||
e.target.value = ' sending ... ';
|
||||
ChatLobbyModel.sendMessage(msg, () => (e.target.value = ''));
|
||||
return false;
|
||||
}
|
||||
},
|
||||
})
|
||||
),
|
||||
]),
|
||||
oninit: () => {
|
||||
ChatLobbyModel.loadLobby(m.route.param('lobby'));
|
||||
window.addEventListener('resize', onResize);
|
||||
},
|
||||
onremove: () => window.removeEventListener('resize', onResize),
|
||||
view: (vnode) => {
|
||||
const chatType = ChatLobbyModel.currentLobby.chatType;
|
||||
const isPrivate = chatType === 1 || chatType === 2;
|
||||
return m(
|
||||
'.node-panel.chat-panel.chat-room',
|
||||
{
|
||||
class:
|
||||
(MobileState.showLobbies ? 'show-lobbies ' : '') +
|
||||
(MobileState.showUsers ? 'show-users ' : '') +
|
||||
(isPrivate ? 'no-lobbies' : ''),
|
||||
},
|
||||
[
|
||||
m('.chat-overlay', { onclick: () => MobileState.closeAll() }),
|
||||
LobbyName(),
|
||||
!isPrivate && m('.lobbies', m(SubscribedLeftLobbies), m(PublicLeftLobbies)),
|
||||
m('.messages', { onclick: () => MobileState.closeAll() }, ChatLobbyModel.messages),
|
||||
m('.rightbar', ChatLobbyModel.users),
|
||||
m(
|
||||
'.chatMessage',
|
||||
{},
|
||||
[
|
||||
m('textarea.chatMsg', {
|
||||
placeholder: 'Type a message...',
|
||||
enterkeyhint: 'send',
|
||||
onkeydown: (e) => {
|
||||
if ((e.key === 'Enter' || e.keyCode === 13) && !e.shiftKey) {
|
||||
const msg = e.target.value;
|
||||
if (msg.trim() === '') return false;
|
||||
e.target.value = ' sending ... ';
|
||||
ChatLobbyModel.sendMessage(msg, () => (e.target.value = ''));
|
||||
return false;
|
||||
}
|
||||
},
|
||||
}),
|
||||
m(
|
||||
'button.chat-send-btn',
|
||||
{
|
||||
onclick: (e) => {
|
||||
const textarea = e.target.closest('.chatMessage').querySelector('textarea');
|
||||
const msg = textarea.value;
|
||||
if (msg.trim() === '') return;
|
||||
textarea.value = ' sending ... ';
|
||||
ChatLobbyModel.sendMessage(msg, () => (textarea.value = ''));
|
||||
},
|
||||
},
|
||||
m('i.fas.fa-paper-plane')
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -445,40 +728,49 @@ const LayoutSetup = () => {
|
||||
return {
|
||||
oninit: () => peopleUtil.ownIds((data) => (ownIds = data)),
|
||||
view: (vnode) =>
|
||||
m('.node-panel', [
|
||||
LobbyName(),
|
||||
m('.lobbies', m(SubscribedLeftLobbies), m(PublicLeftLobbies)),
|
||||
m('.setup', [
|
||||
m('h5.selectidentity', 'Select identity to use'),
|
||||
ownIds.map((nick) =>
|
||||
m(
|
||||
'.identity' +
|
||||
m(
|
||||
'.node-panel.chat-panel.chat-room.chat-setup',
|
||||
{
|
||||
class:
|
||||
(MobileState.showLobbies ? 'show-lobbies ' : '') +
|
||||
(MobileState.showUsers ? 'show-users' : ''),
|
||||
},
|
||||
[
|
||||
m('.chat-overlay', { onclick: () => MobileState.closeAll() }),
|
||||
LobbyName(),
|
||||
m('.lobbies', m(SubscribedLeftLobbies), m(PublicLeftLobbies)),
|
||||
m('.setup', [
|
||||
m('h5.selectidentity', 'Select identity to use'),
|
||||
ownIds.map((nick) =>
|
||||
m(
|
||||
'.identity' +
|
||||
(ChatLobbyModel.currentLobby.gxs_id === nick ? '.selectedidentity' : ''),
|
||||
{
|
||||
onclick: () => ChatLobbyModel.setupAction(m.route.param('lobby'), nick),
|
||||
},
|
||||
rs.userList.username(nick)
|
||||
)
|
||||
),
|
||||
]),
|
||||
]),
|
||||
{
|
||||
onclick: () => ChatLobbyModel.setupAction(m.route.param('lobby'), nick),
|
||||
},
|
||||
rs.userList.username(nick)
|
||||
)
|
||||
),
|
||||
]),
|
||||
]
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
/rsChats/initiateDistantChatConnexion
|
||||
* @param[in] to_pid RsGxsId to start the connection
|
||||
* @param[in] from_pid owned RsGxsId who start the connection
|
||||
* @param[out] pid distant chat id
|
||||
* @param[out] error_code if the connection can't be stablished
|
||||
* @param[in] notify notify remote that the connection is stablished
|
||||
* @param[in] to_pid RsGxsId to start the connection
|
||||
* @param[in] from_pid owned RsGxsId who start the connection
|
||||
* @param[out] pid distant chat id
|
||||
* @param[out] error_code if the connection can't be stablished
|
||||
* @param[in] notify notify remote that the connection is stablished
|
||||
*/
|
||||
const LayoutCreateDistant = () => {
|
||||
let ownIds = [];
|
||||
return {
|
||||
oninit: () => peopleUtil.ownIds((data) => (ownIds = data)),
|
||||
view: (vnode) =>
|
||||
m('.node-panel', [
|
||||
m('.node-panel.chat-panel.chat-room', [
|
||||
m('.createDistantChat', [
|
||||
'choose identitiy to chat with ',
|
||||
rs.userList.username(m.route.param('lobby')),
|
||||
@ -494,9 +786,8 @@ const LayoutCreateDistant = () => {
|
||||
from_pid: id,
|
||||
notify: true,
|
||||
},
|
||||
(result) => {
|
||||
console.info('initiateDistantChatConnexion', result);
|
||||
m.route.set('/chat/:lobbyid', { lobbyid: result.pid });
|
||||
(res) => {
|
||||
m.route.set('/chat/:lobby', { lobby: rs.idToHex(res.pid) });
|
||||
}
|
||||
),
|
||||
},
|
||||
@ -511,7 +802,6 @@ const LayoutCreateDistant = () => {
|
||||
module.exports = {
|
||||
oninit: () => {
|
||||
ChatRoomsModel.loadSubscribedRooms();
|
||||
ChatRoomsModel.loadPublicRooms();
|
||||
},
|
||||
view: (vnode) => {
|
||||
if (m.route.param('lobby') === undefined) {
|
||||
|
||||
@ -149,24 +149,24 @@ const Component = () => {
|
||||
Downloads.resetSearch();
|
||||
},
|
||||
view: () => [
|
||||
m('.widget__body-heading', [
|
||||
m('h3', `Downloads (${Downloads.hashes ? Downloads.hashes.length : 0} files)`),
|
||||
m('.action', [
|
||||
m('.widget__body-heading', { style: { display: 'flex', flexDirection: 'column', alignItems: 'flex-start' } }, [
|
||||
m('.action', { style: { marginBottom: '10px' } }, [
|
||||
m('button', { onclick: () => widget.popupMessage(m(NewFileDialog)) }, 'Add new file'),
|
||||
m('button', { onclick: clearFileCompleted }, 'Clear completed'),
|
||||
]),
|
||||
m('h3', `Downloads (${Downloads.hashes ? Downloads.hashes.length : 0} files)`),
|
||||
]),
|
||||
m('.widget__body-content', [
|
||||
Downloads.statusMap &&
|
||||
Object.keys(Downloads.statusMap).map((hash) =>
|
||||
m(util.File, {
|
||||
info: Downloads.statusMap[hash],
|
||||
strategy: Downloads.strategies[hash],
|
||||
direction: 'down',
|
||||
transferred: Downloads.statusMap[hash].transfered.xint64,
|
||||
chunksInfo: Downloads.chunksMap[hash],
|
||||
})
|
||||
),
|
||||
Object.keys(Downloads.statusMap).map((hash) =>
|
||||
m(util.File, {
|
||||
info: Downloads.statusMap[hash],
|
||||
strategy: Downloads.strategies[hash],
|
||||
direction: 'down',
|
||||
transferred: Downloads.statusMap[hash].transfered.xint64,
|
||||
chunksInfo: Downloads.chunksMap[hash],
|
||||
})
|
||||
),
|
||||
]),
|
||||
],
|
||||
};
|
||||
|
||||
@ -36,11 +36,10 @@ function loadSharedDirectories() {
|
||||
// Update Shared Directories when there is a corresponding event
|
||||
rs.events[rs.RsEventsType.SHARED_DIRECTORIES] = {
|
||||
handler: (event) => {
|
||||
console.log('Shared Directories Event: ', event);
|
||||
switch(event.mEventCode) {
|
||||
case futil.RsSharedDirectoriesEventCode.SHARED_DIRS_LIST_CHANGED:
|
||||
loadSharedDirectories();
|
||||
break;
|
||||
switch (event.mEventCode) {
|
||||
case futil.RsSharedDirectoriesEventCode.SHARED_DIRS_LIST_CHANGED:
|
||||
loadSharedDirectories();
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -156,85 +155,85 @@ const ShareDirTable = () => {
|
||||
m(
|
||||
'tbody.share-manager__table_body',
|
||||
sharedDirArr.length &&
|
||||
sharedDirArr.map((sharedDirItem, index) => {
|
||||
const {
|
||||
filename,
|
||||
virtualname,
|
||||
shareflags,
|
||||
parent_groups: parentGroups,
|
||||
} = sharedDirItem;
|
||||
const sharedFlags = futil.calcIndividualFlags(shareflags);
|
||||
return m('tr', [
|
||||
m(
|
||||
'td',
|
||||
m('input[type=text]', {
|
||||
value: filename,
|
||||
disabled: isEditDisabled,
|
||||
oninput: (e) => {
|
||||
sharedDirArr[index].filename = e.target.value;
|
||||
},
|
||||
})
|
||||
),
|
||||
m(
|
||||
'td',
|
||||
m('input[type=text]', {
|
||||
value: virtualname,
|
||||
disabled: isEditDisabled,
|
||||
oninput: (e) => {
|
||||
sharedDirArr[index].virtualname = e.target.value;
|
||||
},
|
||||
})
|
||||
),
|
||||
m(
|
||||
'td.share-flags',
|
||||
Object.keys(sharedFlags).map((flag) => {
|
||||
return [
|
||||
m(`input.share-flags-check[type=checkbox][id=${flag}]`, {
|
||||
checked: sharedFlags[flag],
|
||||
disabled: isEditDisabled,
|
||||
}),
|
||||
m(
|
||||
`label.share-flags-label[for=${flag}]`,
|
||||
{
|
||||
onclick: () => {
|
||||
if (isEditDisabled) return;
|
||||
sharedFlags[flag] = !sharedFlags[flag];
|
||||
sharedDirArr[index].shareflags = futil.calcShareFlagsValue(sharedFlags);
|
||||
},
|
||||
style: isEditDisabled && { color: '#7D7D7D' },
|
||||
sharedDirArr.map((sharedDirItem, index) => {
|
||||
const {
|
||||
filename,
|
||||
virtualname,
|
||||
shareflags,
|
||||
parent_groups: parentGroups,
|
||||
} = sharedDirItem;
|
||||
const sharedFlags = futil.calcIndividualFlags(shareflags);
|
||||
return m('tr', [
|
||||
m(
|
||||
'td',
|
||||
m('input[type=text]', {
|
||||
value: filename,
|
||||
disabled: isEditDisabled,
|
||||
oninput: (e) => {
|
||||
sharedDirArr[index].filename = e.target.value;
|
||||
},
|
||||
})
|
||||
),
|
||||
m(
|
||||
'td',
|
||||
m('input[type=text]', {
|
||||
value: virtualname,
|
||||
disabled: isEditDisabled,
|
||||
oninput: (e) => {
|
||||
sharedDirArr[index].virtualname = e.target.value;
|
||||
},
|
||||
})
|
||||
),
|
||||
m(
|
||||
'td.share-flags',
|
||||
Object.keys(sharedFlags).map((flag) => {
|
||||
return [
|
||||
m(`input.share-flags-check[type=checkbox][id=${flag}]`, {
|
||||
checked: sharedFlags[flag],
|
||||
disabled: isEditDisabled,
|
||||
}),
|
||||
m(
|
||||
`label.share-flags-label[for=${flag}]`,
|
||||
{
|
||||
onclick: () => {
|
||||
if (isEditDisabled) return;
|
||||
sharedFlags[flag] = !sharedFlags[flag];
|
||||
sharedDirArr[index].shareflags = futil.calcShareFlagsValue(sharedFlags);
|
||||
},
|
||||
m(
|
||||
// check the flag type then if its value is true then only render the icon
|
||||
flag === 'isAnonymousSearch'
|
||||
? sharedFlags[flag]
|
||||
? 'i.fas.fa-search'
|
||||
: 'span'
|
||||
: flag === 'isAnonymousDownload'
|
||||
style: isEditDisabled && { color: '#7D7D7D' },
|
||||
},
|
||||
m(
|
||||
// check the flag type then if its value is true then only render the icon
|
||||
flag === 'isAnonymousSearch'
|
||||
? sharedFlags[flag]
|
||||
? 'i.fas.fa-search'
|
||||
: 'span'
|
||||
: flag === 'isAnonymousDownload'
|
||||
? sharedFlags[flag]
|
||||
? 'i.fas.fa-download'
|
||||
: 'span'
|
||||
: sharedFlags[flag]
|
||||
? 'i.fas.fa-eye'
|
||||
: 'span'
|
||||
)
|
||||
),
|
||||
];
|
||||
})
|
||||
),
|
||||
m(
|
||||
'td',
|
||||
{
|
||||
// since this is not an input element, manually change color
|
||||
style: { color: isEditDisabled ? '#6D6D6D' : 'black' },
|
||||
onclick: () =>
|
||||
!isEditDisabled && widget.popupMessage(m(ManageVisibility, { parentGroups })),
|
||||
},
|
||||
parentGroups.length === 0
|
||||
? 'All Friend nodes'
|
||||
: parentGroups.map((groupFlag) => futil.RsNodeGroupId[groupFlag]).join(', ')
|
||||
),
|
||||
]);
|
||||
})
|
||||
? 'i.fas.fa-eye'
|
||||
: 'span'
|
||||
)
|
||||
),
|
||||
];
|
||||
})
|
||||
),
|
||||
m(
|
||||
'td',
|
||||
{
|
||||
// since this is not an input element, manually change color
|
||||
style: { color: isEditDisabled ? '#6D6D6D' : 'black' },
|
||||
onclick: () =>
|
||||
!isEditDisabled && widget.popupMessage(m(ManageVisibility, { parentGroups })),
|
||||
},
|
||||
parentGroups.length === 0
|
||||
? 'All Friend nodes'
|
||||
: parentGroups.map((groupFlag) => futil.RsNodeGroupId[groupFlag]).join(', ')
|
||||
),
|
||||
]);
|
||||
})
|
||||
),
|
||||
]);
|
||||
},
|
||||
|
||||
@ -8,14 +8,19 @@ const fileProxyObj = futil.createProxy({}, () => {
|
||||
|
||||
rs.events[rs.RsEventsType.FILE_TRANSFER] = {
|
||||
handler: (event) => {
|
||||
console.log('search results : ', event);
|
||||
|
||||
// if request item doesn't already exists in Object then create new item
|
||||
if (!Object.prototype.hasOwnProperty.call(fileProxyObj, event.mRequestId)) {
|
||||
fileProxyObj[event.mRequestId] = [];
|
||||
}
|
||||
|
||||
fileProxyObj[event.mRequestId].push(...event.mResults);
|
||||
event.mResults.forEach((newRes) => {
|
||||
const isAlt = fileProxyObj[event.mRequestId].some(
|
||||
(oldRes) => oldRes.fHash === newRes.fHash && oldRes.fName === newRes.fName
|
||||
);
|
||||
if (!isAlt) {
|
||||
fileProxyObj[event.mRequestId].push(newRes);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ function handleSubmit() {
|
||||
reqObj['_' + res.body.retval] = matchString;
|
||||
currentItem = '_' + res.body.retval;
|
||||
})
|
||||
.catch((error) => console.log(error));
|
||||
.catch((error) => { });
|
||||
}
|
||||
|
||||
const SearchBar = () => {
|
||||
@ -31,6 +31,33 @@ const SearchBar = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const getFileIcon = (fileName) => {
|
||||
const ext = fileName.split('.').pop().toLowerCase();
|
||||
switch (ext) {
|
||||
case 'pdf': return 'i.fas.fa-file-pdf';
|
||||
case 'zip':
|
||||
case 'rar':
|
||||
case 'tar':
|
||||
case 'gz':
|
||||
case '7z': return 'i.fas.fa-file-archive';
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'png':
|
||||
case 'gif': return 'i.fas.fa-file-image';
|
||||
case 'mp4':
|
||||
case 'mkv':
|
||||
case 'avi':
|
||||
case 'mov': return 'i.fas.fa-file-video';
|
||||
case 'mp3':
|
||||
case 'wav':
|
||||
case 'flac': return 'i.fas.fa-file-audio';
|
||||
case 'txt':
|
||||
case 'doc':
|
||||
case 'docx': return 'i.fas.fa-file-alt';
|
||||
default: return 'i.fas.fa-file';
|
||||
}
|
||||
};
|
||||
|
||||
const Layout = () => {
|
||||
let active = 0;
|
||||
function handleFileDownload(item) {
|
||||
@ -54,7 +81,7 @@ const Layout = () => {
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('error in sending download request: ', error);
|
||||
// console.log('error in sending download request: ', error);
|
||||
});
|
||||
}
|
||||
return {
|
||||
@ -63,58 +90,76 @@ const Layout = () => {
|
||||
m('.widget__body', [
|
||||
m('div.file-search-container', [
|
||||
m('div.file-search-container__keywords', [
|
||||
m('h5.bold', 'Keywords'),
|
||||
Object.keys(reqObj).length !== 0 &&
|
||||
m('.keywords-header', [
|
||||
m('h5.bold', 'Keywords'),
|
||||
m(
|
||||
'div.keywords-container',
|
||||
Object.keys(reqObj)
|
||||
.reverse()
|
||||
.map((item, index) => {
|
||||
return m(
|
||||
m.route.Link,
|
||||
{
|
||||
class: active === index ? 'selected' : '',
|
||||
onclick: () => {
|
||||
active = index;
|
||||
currentItem = item;
|
||||
},
|
||||
href: `/files/search/${item}`,
|
||||
},
|
||||
reqObj[item]
|
||||
);
|
||||
})
|
||||
'button.red.clear-btn',
|
||||
{
|
||||
onclick: () => {
|
||||
Object.keys(reqObj).forEach((key) => delete reqObj[key]);
|
||||
Object.keys(fproxy.fileProxyObj).forEach((key) => delete fproxy.fileProxyObj[key]);
|
||||
currentItem = 0;
|
||||
active = 0;
|
||||
},
|
||||
},
|
||||
'Clear'
|
||||
),
|
||||
]),
|
||||
Object.keys(reqObj).length !== 0 &&
|
||||
m(
|
||||
'div.keywords-container',
|
||||
Object.keys(reqObj)
|
||||
.reverse()
|
||||
.map((item, index) => {
|
||||
return m(
|
||||
m.route.Link,
|
||||
{
|
||||
class: active === index ? 'selected' : '',
|
||||
onclick: () => {
|
||||
active = index;
|
||||
currentItem = item;
|
||||
},
|
||||
href: `/files/search/${item}`,
|
||||
},
|
||||
reqObj[item]
|
||||
);
|
||||
})
|
||||
),
|
||||
]),
|
||||
m('div.file-search-container__results', [
|
||||
Object.keys(fproxy.fileProxyObj).length === 0
|
||||
Object.keys(fproxy.fileProxyObj).length === 0 || currentItem === 0
|
||||
? m('h5.bold', 'Results')
|
||||
: m('table.results-container', [
|
||||
m(
|
||||
'thead.results-header',
|
||||
m('tr', [
|
||||
m('th', 'Name'),
|
||||
m('th', 'Size'),
|
||||
m('th', 'Hash'),
|
||||
m('th', 'Download'),
|
||||
])
|
||||
),
|
||||
m(
|
||||
'tbody.results',
|
||||
fproxy.fileProxyObj[currentItem.slice(1)]
|
||||
? fproxy.fileProxyObj[currentItem.slice(1)].map((item) =>
|
||||
m('tr', [
|
||||
m('td.results__name', [m('i.fas.fa-file'), m('span', item.fName)]),
|
||||
m('td.results__size', rs.formatBytes(item.fSize.xint64)),
|
||||
m('td.results__hash', item.fHash),
|
||||
m(
|
||||
'td.results__download',
|
||||
m('button', { onclick: () => handleFileDownload(item) }, 'Download')
|
||||
),
|
||||
])
|
||||
)
|
||||
: 'No Results.'
|
||||
),
|
||||
]),
|
||||
: m('div.results-container', [
|
||||
m(
|
||||
'div.results-header',
|
||||
m('.results-row', [
|
||||
m('.results-cell.name-col', 'Name'),
|
||||
m('.results-cell.size-col', 'Size'),
|
||||
m('.results-cell.hash-col', 'Hash'),
|
||||
m('.results-cell.action-col', 'Download'),
|
||||
])
|
||||
),
|
||||
m(
|
||||
'div.results-list',
|
||||
fproxy.fileProxyObj[currentItem.slice(1)]
|
||||
? fproxy.fileProxyObj[currentItem.slice(1)].map((item) =>
|
||||
m('div.results-row.file-item', [
|
||||
m('.results-cell.name-col', [m(getFileIcon(item.fName)), m('span', item.fName)]),
|
||||
m('.results-cell.size-col', rs.formatBytes((item.fSize && (item.fSize.xint64 || item.fSize.xstr64)) || 0)),
|
||||
m('.results-cell.hash-col', item.fHash),
|
||||
m(
|
||||
'.results-cell.action-col',
|
||||
m(
|
||||
'button.download-btn-v65',
|
||||
{ onclick: () => handleFileDownload(item) },
|
||||
'Download'
|
||||
)
|
||||
),
|
||||
])
|
||||
)
|
||||
: 'No Results.'
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
|
||||
@ -183,9 +183,10 @@ const File = () => {
|
||||
let chunkStrat;
|
||||
const chunkStrats = {
|
||||
// rstypes.h :: 366
|
||||
0: 'Streaming', // CHUNK_STRATEGY_STREAMING
|
||||
0: 'Sequential', // CHUNK_STRATEGY_SEQUENTIAL
|
||||
1: 'Random', // CHUNK_STRATEGY_RANDOM
|
||||
2: 'Progressive', // CHUNK_STRATEGY_PROGRESSIVE
|
||||
3: 'Streaming', // CHUNK_STRATEGY_STREAMING
|
||||
};
|
||||
function fileCancel(hash) {
|
||||
rs.rsJsonApiRequest('/rsFiles/FileCancel', { hash }).then((res) =>
|
||||
@ -220,25 +221,25 @@ const File = () => {
|
||||
});
|
||||
}
|
||||
return m('.file-view', { style: { display: info.isSearched ? 'block' : 'none' } }, [
|
||||
m('.file-view__heading', [
|
||||
m('.file-view__heading', { style: { display: 'flex', flexDirection: 'column', alignItems: 'flex-start' } }, [
|
||||
m('h6', info.fname),
|
||||
chunkStrat !== undefined &&
|
||||
direction === 'down' && [
|
||||
m('.file-view__heading-chunk', [
|
||||
m('label[for=chunkTag]', 'Set Chunk Strategy: '),
|
||||
m('select[id=chunkTag]', { value: chunkStrat, onchange: changeChunkStrategy }, [
|
||||
Object.keys(chunkStrats).map((strat) =>
|
||||
m('option', { value: strat }, chunkStrats[strat])
|
||||
),
|
||||
]),
|
||||
direction === 'down' && [
|
||||
m('.file-view__heading-chunk', [
|
||||
m('label[for=chunkTag]', 'Set Chunk Strategy: '),
|
||||
m('select[id=chunkTag]', { value: chunkStrat, onchange: changeChunkStrategy }, [
|
||||
Object.keys(chunkStrats).map((strat) =>
|
||||
m('option', { value: strat }, chunkStrats[strat])
|
||||
),
|
||||
]),
|
||||
],
|
||||
]),
|
||||
],
|
||||
]),
|
||||
m('.file-view__body', [
|
||||
m(
|
||||
'.file-view__body-progress',
|
||||
direction === 'down' &&
|
||||
m(ProgressBar, { rate: (transferred / info.size.xint64) * 100, chunksInfo })
|
||||
m(ProgressBar, { rate: (transferred / info.size.xint64) * 100, chunksInfo })
|
||||
),
|
||||
m('.file-view__body-details', [
|
||||
m('.file-view__body-details-stat', [
|
||||
@ -255,10 +256,10 @@ const File = () => {
|
||||
`${rs.formatBytes(info.tfRate * 1024)}/s`,
|
||||
]),
|
||||
direction === 'down' &&
|
||||
m('span', { title: 'time remaining' }, [
|
||||
m('i.fas.fa-clock'),
|
||||
calcRemainingTime(info.size.xint64 - transferred, info.tfRate),
|
||||
]),
|
||||
m('span', { title: 'time remaining' }, [
|
||||
m('i.fas.fa-clock'),
|
||||
calcRemainingTime(info.size.xint64 - transferred, info.tfRate),
|
||||
]),
|
||||
m('span', { title: 'peers' }, [m('i.fas.fa-users'), info.peers.length]),
|
||||
]),
|
||||
m(
|
||||
|
||||
@ -3,6 +3,14 @@ const rs = require('rswebui');
|
||||
const util = require('files/files_util');
|
||||
const manager = require('files/files_manager');
|
||||
|
||||
const translateName = (name) => {
|
||||
const n = name.toLowerCase().trim();
|
||||
if (n === 'extra list' || n === '[extra list]') return 'Temporary shared files';
|
||||
// Match hex strings (IDs) or pure numeric strings
|
||||
if (/^[0-9a-fA-F]{16,}$/.test(name) || /^\d+$/.test(name)) return 'My Files';
|
||||
return name;
|
||||
};
|
||||
|
||||
const DisplayFiles = () => {
|
||||
const childrenList = []; // stores children details
|
||||
let loaded = false; // checks whether we have loaded the children details or not.
|
||||
@ -15,28 +23,34 @@ const DisplayFiles = () => {
|
||||
},
|
||||
view: (v) => [
|
||||
m('tr', [
|
||||
parStruct && Object.keys(parStruct.details.children).length
|
||||
parStruct && parStruct.details.children && parStruct.details.children.length
|
||||
? m(
|
||||
'td',
|
||||
m('i.fas.fa-angle-right', {
|
||||
class: `fa-rotate-${parStruct.showChild ? '90' : '0'}`,
|
||||
style: 'margin-top: 0.5rem',
|
||||
onclick: () => {
|
||||
if (!loaded) {
|
||||
// if it is not already retrieved
|
||||
parStruct.details.children.map(async (child) => {
|
||||
const res = await rs.rsJsonApiRequest('/rsfiles/requestDirDetails', {
|
||||
'td',
|
||||
m('i.fas.fa-angle-right', {
|
||||
class: `fa-rotate-${parStruct.showChild ? '90' : '0'}`,
|
||||
style: 'margin-top: 0.5rem',
|
||||
onclick: async () => {
|
||||
if (!loaded) {
|
||||
// if it is not already retrieved
|
||||
const results = await Promise.all(
|
||||
parStruct.details.children.map((child) =>
|
||||
rs.rsJsonApiRequest('/rsfiles/requestDirDetails', {
|
||||
handle: child.handle.xint64,
|
||||
flags: util.RS_FILE_HINTS_LOCAL,
|
||||
});
|
||||
})
|
||||
)
|
||||
);
|
||||
results.forEach((res) => {
|
||||
if (res && res.body && res.body.details) {
|
||||
childrenList.push(res.body.details);
|
||||
loaded = true;
|
||||
});
|
||||
}
|
||||
parStruct.showChild = !parStruct.showChild;
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
});
|
||||
loaded = true;
|
||||
}
|
||||
parStruct.showChild = !parStruct.showChild;
|
||||
},
|
||||
})
|
||||
)
|
||||
: m('td', ''),
|
||||
m(
|
||||
'td',
|
||||
@ -47,29 +61,49 @@ const DisplayFiles = () => {
|
||||
left: `calc(1.5rem*${v.attrs.replyDepth})`,
|
||||
},
|
||||
},
|
||||
parStruct.details.name
|
||||
translateName(parStruct.details.name || '')
|
||||
),
|
||||
m('td', rs.formatBytes(parStruct.details.size.xint64)),
|
||||
m('td', rs.formatBytes((parStruct.details.size && parStruct.details.size.xint64) || 0)),
|
||||
]),
|
||||
parStruct.showChild &&
|
||||
childrenList.map((child) =>
|
||||
m(DisplayFiles, {
|
||||
// recursive call
|
||||
par_directory: { details: child, showChild: false },
|
||||
replyDepth: v.attrs.replyDepth + 1,
|
||||
})
|
||||
),
|
||||
childrenList.map((child) =>
|
||||
m(DisplayFiles, {
|
||||
// recursive call
|
||||
par_directory: { details: child, showChild: false },
|
||||
replyDepth: v.attrs.replyDepth + 1,
|
||||
})
|
||||
),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const Layout = () => {
|
||||
// let root_handle;
|
||||
let parent;
|
||||
let showShareManager = false;
|
||||
let displayList = [];
|
||||
let isLoading = true;
|
||||
let showShareManager = false; // Retain original declaration
|
||||
|
||||
return {
|
||||
oninit: () => {
|
||||
rs.rsJsonApiRequest('/rsfiles/requestDirDetails', {}).then((res) => (parent = res));
|
||||
rs.rsJsonApiRequest('/rsfiles/requestDirDetails', {}).then(async (res) => {
|
||||
if (res && res.body && res.body.details) {
|
||||
if (res.body.details.name === 'root') {
|
||||
// Skip root and fetch full details for each child (Location ID, Extra list, etc)
|
||||
const results = await Promise.all(
|
||||
res.body.details.children.map((child) =>
|
||||
rs.rsJsonApiRequest('/rsfiles/requestDirDetails', {
|
||||
handle: child.handle.xint64,
|
||||
flags: util.RS_FILE_HINTS_LOCAL,
|
||||
})
|
||||
)
|
||||
);
|
||||
displayList = results.map((r) => r.body.details);
|
||||
} else {
|
||||
displayList = [res.body.details];
|
||||
}
|
||||
}
|
||||
isLoading = false;
|
||||
m.redraw();
|
||||
});
|
||||
},
|
||||
view: () => [
|
||||
m('.widget__heading', [
|
||||
@ -81,11 +115,14 @@ const Layout = () => {
|
||||
util.MyFilesTable,
|
||||
m(
|
||||
'tbody',
|
||||
parent &&
|
||||
m(DisplayFiles, {
|
||||
par_directory: { details: parent.body.details, showChild: false },
|
||||
replyDepth: 0,
|
||||
})
|
||||
isLoading
|
||||
? m('tr', m('td[colspan=3]', 'Loading...'))
|
||||
: displayList.map((details) =>
|
||||
m(DisplayFiles, {
|
||||
par_directory: { details, showChild: false },
|
||||
replyDepth: 0,
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
m(
|
||||
|
||||
@ -2,7 +2,7 @@ const m = require('mithril');
|
||||
const rs = require('rswebui');
|
||||
const util = require('forums/forums_util');
|
||||
const peopleUtil = require('people/people_util');
|
||||
const { updatedisplayforums } = require('./forums_util');
|
||||
const { updatedisplayforums, loadPostContent, getTimestampValue, formatTimestamp } = require('./forums_util');
|
||||
|
||||
function createforum() {
|
||||
let title;
|
||||
@ -32,15 +32,15 @@ function createforum() {
|
||||
},
|
||||
[
|
||||
vnode.attrs.authorId &&
|
||||
vnode.attrs.authorId.map((o) =>
|
||||
m(
|
||||
'option',
|
||||
{ value: o },
|
||||
rs.userList.userMap[o]
|
||||
? rs.userList.userMap[o].toLocaleString() + ' (' + o.slice(0, 8) + '...)'
|
||||
: 'No Signature'
|
||||
)
|
||||
),
|
||||
vnode.attrs.authorId.map((o) =>
|
||||
m(
|
||||
'option',
|
||||
{ value: o },
|
||||
rs.userList.username(o)
|
||||
? rs.userList.username(o) + ' (' + o.slice(0, 8) + '...)'
|
||||
: 'No Signature'
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
m('textarea[rows=5][placeholder=Description]', {
|
||||
@ -64,10 +64,10 @@ function createforum() {
|
||||
res.body.retval === false
|
||||
? util.popupmessage([m('h3', 'Error'), m('hr'), m('p', res.body.errorMessage)])
|
||||
: util.popupmessage([
|
||||
m('h3', 'Success'),
|
||||
m('hr'),
|
||||
m('p', 'Forum created successfully'),
|
||||
]);
|
||||
m('h3', 'Success'),
|
||||
m('hr'),
|
||||
m('p', 'Forum created successfully'),
|
||||
]);
|
||||
},
|
||||
},
|
||||
'Create'
|
||||
@ -95,7 +95,7 @@ const EditThread = () => {
|
||||
},
|
||||
[
|
||||
'Identity: ',
|
||||
m('h5[id=authid]', rs.userList.userMap[vnode.attrs.authorId].toLocaleString()),
|
||||
m('h5[id=authid]', rs.userList.username(vnode.attrs.authorId)),
|
||||
]
|
||||
),
|
||||
m(
|
||||
@ -131,10 +131,10 @@ const EditThread = () => {
|
||||
res.body.retval === false
|
||||
? util.popupmessage([m('h3', 'Error'), m('hr'), m('p', res.body.errorMessage)])
|
||||
: util.popupmessage([
|
||||
m('h3', 'Success'),
|
||||
m('hr'),
|
||||
m('p', 'Thread edited successfully'),
|
||||
]);
|
||||
m('h3', 'Success'),
|
||||
m('hr'),
|
||||
m('p', 'Thread edited successfully'),
|
||||
]);
|
||||
util.updatedisplayforums(vnode.attrs.forumId);
|
||||
m.redraw();
|
||||
},
|
||||
@ -175,13 +175,13 @@ const AddThread = () => {
|
||||
},
|
||||
[
|
||||
vnode.attrs.authorId &&
|
||||
vnode.attrs.authorId.map((o) =>
|
||||
m(
|
||||
'option',
|
||||
{ value: o },
|
||||
rs.userList.userMap[o].toLocaleString() + ' (' + o.slice(0, 8) + '...)'
|
||||
)
|
||||
),
|
||||
vnode.attrs.authorId.map((o) =>
|
||||
m(
|
||||
'option',
|
||||
{ value: o },
|
||||
rs.userList.username(o) + ' (' + o.slice(0, 8) + '...)'
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
m('textarea[rows=5]', {
|
||||
@ -196,26 +196,26 @@ const AddThread = () => {
|
||||
const res =
|
||||
(vnode.attrs.parent_thread !== '') > 0 // is it a reply or a new thread
|
||||
? await rs.rsJsonApiRequest('/rsgxsforums/createPost', {
|
||||
forumId: vnode.attrs.forumId,
|
||||
mBody: body,
|
||||
title,
|
||||
authorId: identity,
|
||||
parentId: vnode.attrs.parentId,
|
||||
})
|
||||
forumId: vnode.attrs.forumId,
|
||||
mBody: body,
|
||||
title,
|
||||
authorId: identity,
|
||||
parentId: vnode.attrs.parentId,
|
||||
})
|
||||
: await rs.rsJsonApiRequest('/rsgxsforums/createPost', {
|
||||
forumId: vnode.attrs.forumId,
|
||||
mBody: body,
|
||||
title,
|
||||
authorId: identity,
|
||||
});
|
||||
forumId: vnode.attrs.forumId,
|
||||
mBody: body,
|
||||
title,
|
||||
authorId: identity,
|
||||
});
|
||||
|
||||
res.body.retval === false
|
||||
? util.popupmessage([m('h3', 'Error'), m('hr'), m('p', res.body.errorMessage)])
|
||||
: util.popupmessage([
|
||||
m('h3', 'Success'),
|
||||
m('hr'),
|
||||
m('p', 'Thread added successfully'),
|
||||
]);
|
||||
m('h3', 'Success'),
|
||||
m('hr'),
|
||||
m('p', 'Thread added successfully'),
|
||||
]);
|
||||
util.updatedisplayforums(vnode.attrs.forumId);
|
||||
m.redraw();
|
||||
},
|
||||
@ -225,6 +225,9 @@ const AddThread = () => {
|
||||
]),
|
||||
};
|
||||
};
|
||||
|
||||
// getTimestampValue and formatTimestamp are imported from forums_util.js
|
||||
|
||||
function displaythread() {
|
||||
// recursive function to display all the threads
|
||||
let groupmessagepair;
|
||||
@ -255,15 +258,15 @@ function displaythread() {
|
||||
[
|
||||
Object.keys(parMap).length // if this thread has some replies
|
||||
? m(
|
||||
'td',
|
||||
m('i.fas.fa-angle-right', {
|
||||
class: 'fa-rotate-' + (v.attrs.threadStruct.showReplies ? '90' : '0'),
|
||||
style: 'margin-top:12px',
|
||||
onclick: () => {
|
||||
v.attrs.threadStruct.showReplies = !v.attrs.threadStruct.showReplies;
|
||||
},
|
||||
})
|
||||
)
|
||||
'td',
|
||||
m('i.fas.fa-angle-right', {
|
||||
class: 'fa-rotate-' + (v.attrs.threadStruct.showReplies ? '90' : '0'),
|
||||
style: 'margin-top:12px',
|
||||
onclick: () => {
|
||||
v.attrs.threadStruct.showReplies = !v.attrs.threadStruct.showReplies;
|
||||
},
|
||||
})
|
||||
)
|
||||
: m('td', ''),
|
||||
|
||||
m(
|
||||
@ -273,124 +276,124 @@ function displaythread() {
|
||||
position: 'relative',
|
||||
'--replyDepth': v.attrs.replyDepth,
|
||||
left: 'calc(30px*var(--replyDepth))', // shifts reply by 30 px
|
||||
padding: '10px 0',
|
||||
},
|
||||
onclick: async () => {
|
||||
v.attrs.changeThread(thread.mMeta.mOrigMsgId);
|
||||
if (unread) {
|
||||
const res = await rs.rsJsonApiRequest('/rsgxsforums/markRead', {
|
||||
messageId: groupmessagepair,
|
||||
read: true,
|
||||
});
|
||||
if (res.body.retval) {
|
||||
updatedisplayforums(thread.mMeta.mGroupId);
|
||||
m.redraw();
|
||||
}
|
||||
}
|
||||
},
|
||||
ondblclick: () =>
|
||||
(v.attrs.threadStruct.showReplies = !v.attrs.threadStruct.showReplies),
|
||||
},
|
||||
[
|
||||
thread.mMeta.mMsgName,
|
||||
m('options', { style: 'display:block' }, [
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
style: 'font-size:15px',
|
||||
onclick: () =>
|
||||
util.popupmessage(
|
||||
m(AddThread, {
|
||||
parent_thread: thread.mMeta.mMsgName,
|
||||
forumId: thread.mMeta.mGroupId,
|
||||
authorId: v.attrs.identity,
|
||||
parentId: thread.mMeta.mMsgId,
|
||||
})
|
||||
),
|
||||
},
|
||||
'Reply'
|
||||
),
|
||||
editpermission &&
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
style: 'font-size:15px',
|
||||
onclick: () =>
|
||||
util.popupmessage(
|
||||
m(EditThread, {
|
||||
current_thread: thread.mMeta.mMsgName,
|
||||
forumId: thread.mMeta.mGroupId,
|
||||
current_title: thread.mMeta.mMsgName,
|
||||
current_body: thread.mMsg,
|
||||
authorId: thread.mMeta.mAuthorId,
|
||||
current_parent: thread.mMeta.mParentId,
|
||||
current_msgid: thread.mMeta.mOrigMsgId,
|
||||
})
|
||||
),
|
||||
},
|
||||
'Edit'
|
||||
),
|
||||
]),
|
||||
]
|
||||
),
|
||||
m(
|
||||
'td',
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
style: { fontSize: '15px' },
|
||||
m('div.date', { style: { fontSize: '0.8em', color: '#888' } },
|
||||
formatTimestamp(thread.mMeta.mPublishTs)
|
||||
),
|
||||
m('div.title', {
|
||||
style: { fontWeight: 'bold', fontSize: '1.1em', cursor: 'pointer', margin: '5px 0' },
|
||||
onclick: async () => {
|
||||
if (!unread) {
|
||||
v.attrs.changeThread(thread.mMeta.mOrigMsgId);
|
||||
if (unread) {
|
||||
const res = await rs.rsJsonApiRequest('/rsgxsforums/markRead', {
|
||||
messageId: groupmessagepair,
|
||||
read: false,
|
||||
read: true,
|
||||
});
|
||||
|
||||
if (res.body.retval) {
|
||||
updatedisplayforums(thread.mMeta.mGroupId);
|
||||
m.redraw();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
'Mark Unread'
|
||||
)
|
||||
),
|
||||
m('td', rs.userList.userMap[thread.mMeta.mAuthorId]),
|
||||
m(
|
||||
'td',
|
||||
typeof thread.mMeta.mPublishTs === 'object'
|
||||
? new Date(thread.mMeta.mPublishTs.xint64 * 1000).toLocaleString()
|
||||
: 'undefined'
|
||||
ondblclick: () =>
|
||||
(v.attrs.threadStruct.showReplies = !v.attrs.threadStruct.showReplies),
|
||||
}, [
|
||||
thread.mMeta.mMsgName,
|
||||
m('options', { style: 'display:block; margin-top: 5px;' }, [
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
style: 'font-size:12px; margin-right: 5px;',
|
||||
onclick: (e) => {
|
||||
e.stopPropagation();
|
||||
util.popupmessage(
|
||||
m(AddThread, {
|
||||
parent_thread: thread.mMeta.mMsgName,
|
||||
forumId: thread.mMeta.mGroupId,
|
||||
authorId: v.attrs.identity,
|
||||
parentId: thread.mMeta.mMsgId,
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
'Reply'
|
||||
),
|
||||
editpermission &&
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
style: 'font-size:12px; margin-right: 5px;',
|
||||
onclick: async (e) => {
|
||||
e.stopPropagation();
|
||||
const body = await loadPostContent(
|
||||
thread.mMeta.mGroupId,
|
||||
thread.mMeta.mOrigMsgId
|
||||
);
|
||||
util.popupmessage(
|
||||
m(EditThread, {
|
||||
current_thread: thread.mMeta.mMsgName,
|
||||
forumId: thread.mMeta.mGroupId,
|
||||
current_title: thread.mMeta.mMsgName,
|
||||
current_body: body || '',
|
||||
authorId: thread.mMeta.mAuthorId,
|
||||
current_parent: thread.mMeta.mParentId,
|
||||
current_msgid: thread.mMeta.mOrigMsgId,
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
'Edit'
|
||||
),
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
style: { fontSize: '12px' },
|
||||
onclick: async (e) => {
|
||||
e.stopPropagation();
|
||||
const res = await rs.rsJsonApiRequest('/rsgxsforums/markRead', {
|
||||
messageId: groupmessagepair,
|
||||
read: !unread ? true : false,
|
||||
});
|
||||
|
||||
if (res.body.retval) {
|
||||
updatedisplayforums(thread.mMeta.mGroupId);
|
||||
m.redraw();
|
||||
}
|
||||
},
|
||||
},
|
||||
unread ? 'Mark Read' : 'Mark Unread'
|
||||
),
|
||||
]),
|
||||
]),
|
||||
m('div.author', { style: { fontSize: '0.9em', fontStyle: 'italic' } }, rs.userList.username(thread.mMeta.mAuthorId)),
|
||||
]
|
||||
),
|
||||
]
|
||||
),
|
||||
v.attrs.threadStruct.showReplies &&
|
||||
Object.keys(parMap).map((key, index) =>
|
||||
m(displaythread, {
|
||||
// recursive call to all replies
|
||||
threadStruct: util.Data.Threads[parMap[key].mGroupId][parMap[key].mOrigMsgId],
|
||||
replyDepth: v.attrs.replyDepth + 1,
|
||||
identity: v.attrs.identity,
|
||||
changeThread: v.attrs.changeThread,
|
||||
})
|
||||
),
|
||||
Object.keys(parMap).map((key, index) =>
|
||||
m(displaythread, {
|
||||
// recursive call to all replies
|
||||
threadStruct: util.Data.Threads[parMap[key].mGroupId][parMap[key].mOrigMsgId],
|
||||
replyDepth: v.attrs.replyDepth + 1,
|
||||
identity: v.attrs.identity,
|
||||
changeThread: v.attrs.changeThread,
|
||||
})
|
||||
),
|
||||
];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const ThreadView = () => {
|
||||
let thread = {};
|
||||
let ownId;
|
||||
return {
|
||||
showThread: '',
|
||||
oninit: async (v) => {
|
||||
if (
|
||||
util.Data.ParentThreads[v.attrs.forumId] &&
|
||||
util.Data.ParentThreads[v.attrs.forumId][v.attrs.msgId]
|
||||
) {
|
||||
thread = util.Data.ParentThreads[v.attrs.forumId][v.attrs.msgId];
|
||||
}
|
||||
oninit: (v) => {
|
||||
util.updatedisplayforums(v.attrs.forumId);
|
||||
peopleUtil.ownIds((data) => {
|
||||
ownId = data;
|
||||
for (let i = 0; i < ownId.length; i++) {
|
||||
@ -400,76 +403,94 @@ const ThreadView = () => {
|
||||
}
|
||||
});
|
||||
},
|
||||
view: (v) =>
|
||||
m('.widget', { key: v.attrs.msgId }, [
|
||||
view: (v) => {
|
||||
const forumId = v.attrs.forumId;
|
||||
const msgId = v.attrs.msgId;
|
||||
const threadStruct = (util.Data.Threads[forumId] && util.Data.Threads[forumId][msgId]) ? util.Data.Threads[forumId][msgId] : null;
|
||||
|
||||
if (!threadStruct) {
|
||||
return m('.widget', [
|
||||
m(
|
||||
'a[title=Back]',
|
||||
{
|
||||
onclick: () => m.route.set('/forums/:tab/:mGroupId', {
|
||||
tab: m.route.param().tab,
|
||||
mGroupId: forumId,
|
||||
}),
|
||||
},
|
||||
m('i.fas.fa-arrow-left')
|
||||
),
|
||||
m('h3', 'Loading...'),
|
||||
]);
|
||||
}
|
||||
|
||||
const meta = threadStruct.thread.mMeta;
|
||||
const unread = meta.mMsgStatus === util.THREAD_UNREAD;
|
||||
|
||||
return m('.widget', { key: msgId }, [
|
||||
m(
|
||||
'a[title=Back]',
|
||||
{
|
||||
onclick: () =>
|
||||
m.route.set('/forums/:tab/:mGroupId', {
|
||||
tab: m.route.param().tab,
|
||||
mGroupId: m.route.param().mGroupId,
|
||||
}),
|
||||
onclick: () => m.route.set('/forums/:tab/:mGroupId', {
|
||||
tab: m.route.param().tab,
|
||||
mGroupId: forumId,
|
||||
}),
|
||||
},
|
||||
m('i.fas.fa-arrow-left')
|
||||
),
|
||||
m('h3', thread.mMsgName),
|
||||
m('div.post-header', { style: { margin: '10px 0' } }, [
|
||||
m('div.date', { style: { color: '#888', fontSize: '0.9em' } }, formatTimestamp(meta.mPublishTs)),
|
||||
m('h4.title', { style: { margin: '5px 0', fontWeight: 'bold' } }, meta.mMsgName),
|
||||
m('div.author', { style: { fontStyle: 'italic', fontSize: '1em' } }, rs.userList.username(meta.mAuthorId)),
|
||||
]),
|
||||
m('hr'),
|
||||
m(
|
||||
util.ThreadsReplyTable,
|
||||
m(
|
||||
'tbody',
|
||||
util.Data.Threads[v.attrs.forumId] &&
|
||||
util.Data.Threads[v.attrs.forumId][v.attrs.msgId] &&
|
||||
m(displaythread, {
|
||||
threadStruct: util.Data.Threads[v.attrs.forumId][v.attrs.msgId],
|
||||
replyDepth: 0,
|
||||
identity: ownId,
|
||||
changeThread(newThread) {
|
||||
v.state.showThread = newThread;
|
||||
// For displaying the messages of the threads. We pass this into the recursive function displaythreads()
|
||||
},
|
||||
})
|
||||
)
|
||||
),
|
||||
m('hr'),
|
||||
v.state.showThread && [
|
||||
m('h4', 'Messages'),
|
||||
util.Data.Threads[v.attrs.forumId] &&
|
||||
util.Data.Threads[v.attrs.forumId][v.state.showThread] &&
|
||||
m('p', m.trust(util.Data.Threads[v.attrs.forumId][v.state.showThread].thread.mMsg)),
|
||||
// m.trust is to render html content directly.
|
||||
],
|
||||
]),
|
||||
m('div.actions', { style: { marginBottom: '15px' } }, [
|
||||
m('button', {
|
||||
style: { marginRight: '10px' },
|
||||
onclick: () => util.popupmessage(m(AddThread, {
|
||||
parent_thread: meta.mMsgName,
|
||||
forumId: forumId,
|
||||
authorId: ownId,
|
||||
parentId: msgId,
|
||||
}))
|
||||
}, 'Reply'),
|
||||
m('button', {
|
||||
onclick: async () => {
|
||||
const res = await rs.rsJsonApiRequest('/rsgxsforums/markRead', {
|
||||
messageId: { first: forumId, second: meta.mOrigMsgId },
|
||||
read: !unread,
|
||||
});
|
||||
if (res.body.retval) {
|
||||
util.updatedisplayforums(forumId);
|
||||
m.redraw();
|
||||
}
|
||||
}
|
||||
}, unread ? 'Mark Read' : 'Mark Unread'),
|
||||
]),
|
||||
m('div.content', {
|
||||
style: {
|
||||
width: '100%',
|
||||
backgroundColor: '#f9f9f9',
|
||||
padding: '15px',
|
||||
borderRadius: '5px',
|
||||
whiteSpace: 'pre-wrap', // Preserve line breaks
|
||||
wordBreak: 'break-word',
|
||||
}
|
||||
}, [
|
||||
threadStruct.thread.mMsg !== null
|
||||
? m.trust(threadStruct.thread.mMsg)
|
||||
: (loadPostContent(forumId, msgId), m('p', 'Loading content...'))
|
||||
]),
|
||||
]);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const ForumView = () => {
|
||||
let fname = '';
|
||||
let fauthor = '';
|
||||
let fsubscribed = {};
|
||||
let createDate = {};
|
||||
let lastActivity = {};
|
||||
let topThreads = {};
|
||||
let ownId = '';
|
||||
return {
|
||||
oninit: (v) => {
|
||||
if (util.Data.DisplayForums[v.attrs.id]) {
|
||||
fname = util.Data.DisplayForums[v.attrs.id].name;
|
||||
fsubscribed = util.Data.DisplayForums[v.attrs.id].isSubscribed;
|
||||
createDate = util.Data.DisplayForums[v.attrs.id].created;
|
||||
lastActivity = util.Data.DisplayForums[v.attrs.id].activity;
|
||||
if (rs.userList.userMap[util.Data.DisplayForums[v.attrs.id].author]) {
|
||||
fauthor = rs.userList.userMap[util.Data.DisplayForums[v.attrs.id].author];
|
||||
} else if (Number(util.Data.DisplayForums[v.attrs.id].author) === 0) {
|
||||
fauthor = 'No Contact Author';
|
||||
} else {
|
||||
fauthor = 'Unknown';
|
||||
}
|
||||
}
|
||||
if (util.Data.ParentThreads[v.attrs.id]) {
|
||||
topThreads = util.Data.ParentThreads[v.attrs.id];
|
||||
}
|
||||
util.updatedisplayforums(v.attrs.id);
|
||||
peopleUtil.ownIds((data) => {
|
||||
ownId = data;
|
||||
for (let i = 0; i < ownId.length; i++) {
|
||||
@ -479,117 +500,133 @@ const ForumView = () => {
|
||||
}
|
||||
});
|
||||
},
|
||||
view: (v) => [
|
||||
m(
|
||||
'a[title=Back]',
|
||||
{
|
||||
onclick: () =>
|
||||
m.route.set('/forums/:tab', {
|
||||
tab: m.route.param().tab,
|
||||
}),
|
||||
},
|
||||
m('i.fas.fa-arrow-left')
|
||||
),
|
||||
view: (v) => {
|
||||
const forumDetails = util.Data.DisplayForums[v.attrs.id] || {
|
||||
name: 'Loading...',
|
||||
isSubscribed: false,
|
||||
created: {},
|
||||
activity: {},
|
||||
author: '0',
|
||||
description: 'Loading...',
|
||||
};
|
||||
const allPosts = util.Data.Threads[v.attrs.id]
|
||||
? Object.values(util.Data.Threads[v.attrs.id]).map((ts) => ts.thread.mMeta)
|
||||
: [];
|
||||
const fname = forumDetails.name;
|
||||
const fsubscribed = forumDetails.isSubscribed;
|
||||
const createDate = forumDetails.created;
|
||||
const lastActivity = forumDetails.activity;
|
||||
let fauthor = 'Unknown';
|
||||
|
||||
m('h3', fname),
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
onclick: async () => {
|
||||
const res = await rs.rsJsonApiRequest('/rsgxsforums/subscribeToForum', {
|
||||
forumId: v.attrs.id,
|
||||
subscribe: !fsubscribed,
|
||||
});
|
||||
if (res.body.retval) {
|
||||
fsubscribed = !fsubscribed;
|
||||
util.Data.DisplayForums[v.attrs.id].isSubscribed = fsubscribed;
|
||||
}
|
||||
if (rs.userList.userMap[forumDetails.author]) {
|
||||
fauthor = rs.userList.userMap[forumDetails.author];
|
||||
} else if (Number(forumDetails.author) === 0) {
|
||||
fauthor = 'No Contact Author';
|
||||
}
|
||||
|
||||
return [
|
||||
m(
|
||||
'a[title=Back]',
|
||||
{
|
||||
onclick: () =>
|
||||
m.route.set('/forums/:tab', {
|
||||
tab: m.route.param().tab,
|
||||
}),
|
||||
},
|
||||
},
|
||||
fsubscribed ? 'Subscribed' : 'Subscribe'
|
||||
),
|
||||
m('[id=forumdetails]', [
|
||||
m(
|
||||
'p',
|
||||
m('b', 'Date created: '),
|
||||
typeof createDate === 'object'
|
||||
? new Date(createDate.xint64 * 1000).toLocaleString()
|
||||
: 'undefined'
|
||||
m('i.fas.fa-arrow-left')
|
||||
),
|
||||
m('p', m('b', 'Admin: '), fauthor),
|
||||
m(
|
||||
'p',
|
||||
m('b', 'Last activity: '),
|
||||
typeof lastActivity === 'object'
|
||||
? new Date(lastActivity.xint64 * 1000).toLocaleString()
|
||||
: 'undefined'
|
||||
),
|
||||
]),
|
||||
m('hr'),
|
||||
m('forumdesc', m('b', 'Description: '), util.Data.DisplayForums[v.attrs.id].description),
|
||||
m('hr'),
|
||||
m(
|
||||
'threaddetails',
|
||||
{
|
||||
style: 'display:' + (fsubscribed ? 'block' : 'none'),
|
||||
},
|
||||
m('h3', 'Threads'),
|
||||
|
||||
m('h3', fname),
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
onclick: () => {
|
||||
util.popupmessage(
|
||||
m(AddThread, {
|
||||
parent_thread: '',
|
||||
forumId: v.attrs.id,
|
||||
authorId: ownId,
|
||||
parentId: '',
|
||||
})
|
||||
);
|
||||
onclick: async () => {
|
||||
const res = await rs.rsJsonApiRequest('/rsgxsforums/subscribeToForum', {
|
||||
forumId: v.attrs.id,
|
||||
subscribe: !fsubscribed,
|
||||
});
|
||||
if (res.body.retval) {
|
||||
util.Data.DisplayForums[v.attrs.id].isSubscribed = !fsubscribed;
|
||||
}
|
||||
},
|
||||
},
|
||||
['New Thread', m('i.fas.fa-pencil-alt')]
|
||||
fsubscribed ? 'Subscribed' : 'Subscribe'
|
||||
),
|
||||
m('[id=forumdetails]', [
|
||||
m(
|
||||
'p',
|
||||
m('b', 'Date created: '),
|
||||
formatTimestamp(createDate)
|
||||
),
|
||||
m('p', m('b', 'Admin: '), fauthor),
|
||||
m(
|
||||
'p',
|
||||
m('b', 'Last activity: '),
|
||||
formatTimestamp(lastActivity)
|
||||
),
|
||||
]),
|
||||
m('hr'),
|
||||
m('forumdesc', m('b', 'Description: '), forumDetails.description),
|
||||
m('hr'),
|
||||
m(
|
||||
util.ThreadsTable,
|
||||
'threaddetails',
|
||||
{
|
||||
style: 'display:' + (fsubscribed ? 'block' : 'none'),
|
||||
},
|
||||
m('h3', 'Threads'),
|
||||
m(
|
||||
'tbody',
|
||||
Object.keys(topThreads).map((key, index) =>
|
||||
m(
|
||||
'tr',
|
||||
{
|
||||
style:
|
||||
topThreads[key].mMsgStatus === util.THREAD_UNREAD ? { fontWeight: 'bold' } : '',
|
||||
onclick: () => {
|
||||
m.route.set('/forums/:tab/:mGroupId/:mMsgId', {
|
||||
tab: m.route.param().tab,
|
||||
mGroupId: v.attrs.id,
|
||||
mMsgId: topThreads[key].mOrigMsgId,
|
||||
});
|
||||
},
|
||||
},
|
||||
[
|
||||
m('td', topThreads[key].mMsgName),
|
||||
'button',
|
||||
{
|
||||
onclick: () => {
|
||||
util.popupmessage(
|
||||
m(AddThread, {
|
||||
parent_thread: '',
|
||||
forumId: v.attrs.id,
|
||||
authorId: ownId,
|
||||
parentId: '',
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
['New Thread', m('i.fas.fa-pencil-alt')]
|
||||
),
|
||||
m('hr'),
|
||||
m(
|
||||
util.ThreadsTable,
|
||||
m(
|
||||
'tbody',
|
||||
allPosts
|
||||
.sort((a, b) => getTimestampValue(b.mPublishTs) - getTimestampValue(a.mPublishTs))
|
||||
.map((thread) =>
|
||||
m(
|
||||
'td',
|
||||
typeof topThreads[key].mPublishTs === 'object'
|
||||
? new Date(topThreads[key].mPublishTs.xint64 * 1000).toLocaleString()
|
||||
: 'undefined'
|
||||
),
|
||||
m(
|
||||
'td',
|
||||
rs.userList.userMap[topThreads[key].mAuthorId]
|
||||
? rs.userList.userMap[topThreads[key].mAuthorId]
|
||||
: 'Unknown'
|
||||
),
|
||||
]
|
||||
)
|
||||
'tr',
|
||||
{
|
||||
style:
|
||||
thread.mMsgStatus === util.THREAD_UNREAD ? { fontWeight: 'bold' } : '',
|
||||
},
|
||||
m('td', { style: { padding: '10px 0' } }, [
|
||||
m('div.date', { style: { fontSize: '0.8em', color: '#888' } },
|
||||
formatTimestamp(thread.mPublishTs)
|
||||
),
|
||||
m('div.title', {
|
||||
style: { fontWeight: 'bold', fontSize: '1.2em', cursor: 'pointer', margin: '5px 0' },
|
||||
onclick: () => {
|
||||
m.route.set('/forums/:tab/:mGroupId/:mMsgId', {
|
||||
tab: m.route.param().tab,
|
||||
mGroupId: v.attrs.id,
|
||||
mMsgId: thread.mOrigMsgId,
|
||||
});
|
||||
},
|
||||
}, thread.mMsgName),
|
||||
m('div.author', { style: { fontSize: '0.9em', fontStyle: 'italic' } }, rs.userList.username(thread.mAuthorId)),
|
||||
])
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -12,16 +12,18 @@ const getForums = {
|
||||
MyForums: [],
|
||||
async load() {
|
||||
const res = await rs.rsJsonApiRequest('/rsgxsforums/getForumsSummaries');
|
||||
getForums.All = res.body.forums;
|
||||
getForums.PopularForums = getForums.All;
|
||||
getForums.SubscribedForums = getForums.All.filter(
|
||||
(forum) =>
|
||||
forum.mSubscribeFlags === util.GROUP_SUBSCRIBE_SUBSCRIBED ||
|
||||
forum.mSubscribeFlags === util.GROUP_MY_FORUM
|
||||
);
|
||||
getForums.MyForums = getForums.All.filter(
|
||||
(forum) => forum.mSubscribeFlags === util.GROUP_MY_FORUM
|
||||
);
|
||||
if (res && res.body && res.body.forums) {
|
||||
getForums.All = res.body.forums;
|
||||
getForums.PopularForums = getForums.All;
|
||||
getForums.SubscribedForums = getForums.All.filter(
|
||||
(forum) =>
|
||||
forum.mSubscribeFlags === util.GROUP_SUBSCRIBE_SUBSCRIBED ||
|
||||
forum.mSubscribeFlags === util.GROUP_MY_FORUM
|
||||
);
|
||||
getForums.MyForums = getForums.All.filter(
|
||||
(forum) => forum.mSubscribeFlags === util.GROUP_MY_FORUM
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
const sections = {
|
||||
@ -37,7 +39,7 @@ const Layout = () => {
|
||||
return {
|
||||
oninit: () => {
|
||||
rs.setBackgroundTask(getForums.load, 5000, () => {
|
||||
// return m.route.get() === '/files/files';
|
||||
return m.route.get().includes('/forums');
|
||||
});
|
||||
peopleUtil.ownIds((data) => {
|
||||
ownId = data;
|
||||
@ -52,6 +54,7 @@ const Layout = () => {
|
||||
view: (vnode) =>
|
||||
m('.widget', [
|
||||
m('.top-heading', [
|
||||
vnode.attrs.pathInfo.tab === 'MyForums' &&
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
@ -71,14 +74,14 @@ const Layout = () => {
|
||||
]),
|
||||
Object.prototype.hasOwnProperty.call(vnode.attrs.pathInfo, 'mMsgId') // thread's view
|
||||
? m(viewUtil.ThreadView, {
|
||||
msgId: vnode.attrs.pathInfo.mMsgId,
|
||||
forumId: vnode.attrs.pathInfo.mGroupId,
|
||||
})
|
||||
msgId: vnode.attrs.pathInfo.mMsgId,
|
||||
forumId: vnode.attrs.pathInfo.mGroupId,
|
||||
})
|
||||
: Object.prototype.hasOwnProperty.call(vnode.attrs.pathInfo, 'mGroupId') // Forum's view
|
||||
? m(viewUtil.ForumView, {
|
||||
? m(viewUtil.ForumView, {
|
||||
id: vnode.attrs.pathInfo.mGroupId,
|
||||
})
|
||||
: m(sections[vnode.attrs.pathInfo.tab], {
|
||||
: m(sections[vnode.attrs.pathInfo.tab], {
|
||||
list: getForums[vnode.attrs.pathInfo.tab],
|
||||
}),
|
||||
]),
|
||||
|
||||
@ -14,76 +14,166 @@ const Data = {
|
||||
Threads: {},
|
||||
ParentThreads: {},
|
||||
ParentThreadMap: {},
|
||||
loading: new Set(),
|
||||
};
|
||||
|
||||
async function updatedisplayforums(keyid, details = {}) {
|
||||
const res = await rs.rsJsonApiRequest('/rsgxsforums/getForumsInfo', {
|
||||
forumIds: [keyid], // keyid: Forumid
|
||||
});
|
||||
details = res.body.forumsInfo[0];
|
||||
Data.DisplayForums[keyid] = {
|
||||
// struct for a forum
|
||||
name: details.mMeta.mGroupName,
|
||||
author: details.mMeta.mAuthorId,
|
||||
isSearched: true,
|
||||
description: details.mDescription,
|
||||
isSubscribed:
|
||||
details.mMeta.mSubscribeFlags === GROUP_SUBSCRIBE_SUBSCRIBED ||
|
||||
details.mMeta.mSubscribeFlags === GROUP_MY_FORUM,
|
||||
activity: details.mMeta.mLastPost,
|
||||
created: details.mMeta.mPublishTs,
|
||||
};
|
||||
if (Data.Threads[keyid] === undefined) {
|
||||
Data.Threads[keyid] = {};
|
||||
function getTimestampValue(ts) {
|
||||
if (!ts) return 0;
|
||||
if (typeof ts === 'object') {
|
||||
if (ts.xint64 !== undefined) return ts.xint64;
|
||||
if (ts.xstr64 !== undefined) return Number(ts.xstr64);
|
||||
return 0;
|
||||
}
|
||||
const res2 = await rs.rsJsonApiRequest('/rsgxsforums/getForumMsgMetaData', {
|
||||
forumId: keyid,
|
||||
});
|
||||
if (res2.body.retval) {
|
||||
res2.body.msgMetas.map(async (thread) => {
|
||||
const res3 = await rs.rsJsonApiRequest('/rsgxsforums/getForumContent', {
|
||||
forumId: keyid,
|
||||
msgsIds: [thread.mMsgId],
|
||||
return ts;
|
||||
}
|
||||
|
||||
function formatTimestamp(ts) {
|
||||
const val = getTimestampValue(ts);
|
||||
if (!val || val === 0) return '???';
|
||||
try {
|
||||
const localDate = new Date(val * 1000);
|
||||
const offset = localDate.getTimezoneOffset() * 60000;
|
||||
return new Date(localDate.getTime() - offset).toISOString().replace('T', ' ').slice(0, 16);
|
||||
} catch (e) {
|
||||
return 'Invalid Date';
|
||||
}
|
||||
}
|
||||
|
||||
async function updatedisplayforums(keyid) {
|
||||
if (Data.loading.has(keyid)) return;
|
||||
Data.loading.add(keyid);
|
||||
|
||||
try {
|
||||
const res1 = await rs.rsJsonApiRequest('/rsgxsforums/getForumsInfo', {
|
||||
forumIds: [keyid], // keyid: Forumid
|
||||
});
|
||||
if (res1 && res1.body && res1.body.retval && res1.body.forumsInfo && res1.body.forumsInfo.length > 0) {
|
||||
const forumInfo = res1.body.forumsInfo[0];
|
||||
Data.DisplayForums[keyid] = {
|
||||
// struct for a forum
|
||||
name: forumInfo.mMeta.mGroupName,
|
||||
author: forumInfo.mMeta.mAuthorId,
|
||||
isSearched: true,
|
||||
description: forumInfo.mDescription,
|
||||
isSubscribed:
|
||||
forumInfo.mMeta.mSubscribeFlags === GROUP_SUBSCRIBE_SUBSCRIBED ||
|
||||
forumInfo.mMeta.mSubscribeFlags === GROUP_MY_FORUM,
|
||||
activity: forumInfo.mMeta.mLastPost,
|
||||
created: forumInfo.mMeta.mPublishTs,
|
||||
};
|
||||
if (Data.Threads[keyid] === undefined) {
|
||||
Data.Threads[keyid] = {};
|
||||
}
|
||||
|
||||
const res2 = await rs.rsJsonApiRequest('/rsgxsforums/getForumPostsHierarchy', {
|
||||
group: forumInfo,
|
||||
});
|
||||
|
||||
if (
|
||||
res3.body.retval &&
|
||||
(Data.Threads[keyid][thread.mOrigMsgId] === undefined ||
|
||||
Data.Threads[keyid][thread.mOrigMsgId].thread.mMeta.mPublishTs.xint64 <
|
||||
thread.mPublishTs.xint64)
|
||||
// here we get the latest edited thread for each thread by comparing the publish time
|
||||
) {
|
||||
Data.Threads[keyid][thread.mOrigMsgId] = { thread: res3.body.msgs[0], showReplies: false };
|
||||
if (
|
||||
Data.Threads[keyid][thread.mOrigMsgId] &&
|
||||
Data.Threads[keyid][thread.mOrigMsgId].thread.mMeta.mMsgStatus === THREAD_UNREAD
|
||||
) {
|
||||
let parent = Data.Threads[keyid][thread.mOrigMsgId].thread.mMeta.mParentId;
|
||||
while (Data.Threads[keyid][parent]) {
|
||||
// to mark all parent threads of an inread thread
|
||||
Data.Threads[keyid][parent].thread.mMeta.mMsgStatus = THREAD_UNREAD;
|
||||
parent = Data.Threads[keyid][parent].thread.mMeta.mParentId;
|
||||
}
|
||||
}
|
||||
if (res2 && res2.body && res2.body.vect) {
|
||||
const vect = res2.body.vect;
|
||||
// Index 0 is the root sentinel in GXS hierarchy
|
||||
const rootSentinel = vect[0];
|
||||
|
||||
if (Data.ParentThreads[keyid] === undefined) {
|
||||
if (rootSentinel && rootSentinel.mChildren) {
|
||||
Data.ParentThreads[keyid] = {};
|
||||
}
|
||||
if (thread.mThreadId === thread.mParentId) {
|
||||
// top level thread.
|
||||
Data.ParentThreads[keyid][thread.mOrigMsgId] =
|
||||
Data.Threads[keyid][thread.mOrigMsgId].thread.mMeta;
|
||||
} else {
|
||||
if (Data.ParentThreadMap[thread.mParentId] === undefined) {
|
||||
Data.ParentThreadMap[thread.mParentId] = {};
|
||||
}
|
||||
Data.ParentThreadMap[thread.mParentId][thread.mOrigMsgId] = thread;
|
||||
rootSentinel.mChildren.forEach((topIndex) => {
|
||||
const EntryToThread = (entryIndex) => {
|
||||
const entry = vect[entryIndex];
|
||||
const replies = {};
|
||||
|
||||
// Map ForumPostEntry to a structure compatible with the existing UI
|
||||
const meta = {
|
||||
mGroupId: keyid,
|
||||
mMsgId: entry.mMsgId,
|
||||
mOrigMsgId: entry.mMsgId,
|
||||
mThreadId: entry.mMsgId,
|
||||
mParentId:
|
||||
entry.mParent !== 0
|
||||
? vect[entry.mParent].mMsgId
|
||||
: '00000000000000000000000000000000',
|
||||
mAuthorId: entry.mAuthorId,
|
||||
mMsgName: entry.mTitle,
|
||||
mPublishTs: entry.mPublishTs,
|
||||
mMostRecentTsInThread: getTimestampValue(entry.mPublishTs),
|
||||
mMsgStatus: entry.mMsgStatus,
|
||||
};
|
||||
|
||||
// Populate ParentThreadMap for compatibility
|
||||
if (meta.mParentId !== '00000000000000000000000000000000') {
|
||||
if (!Data.ParentThreadMap[meta.mParentId]) Data.ParentThreadMap[meta.mParentId] = {};
|
||||
Data.ParentThreadMap[meta.mParentId][meta.mMsgId] = meta;
|
||||
}
|
||||
|
||||
const threadStruct = {
|
||||
thread: { mMeta: meta, mMsg: null },
|
||||
replies: replies,
|
||||
showReplies: false,
|
||||
};
|
||||
|
||||
// Add to flat map
|
||||
Data.Threads[keyid][meta.mMsgId] = threadStruct;
|
||||
|
||||
if (entry.mChildren) {
|
||||
entry.mChildren.forEach((childIndex) => {
|
||||
const childThread = EntryToThread(childIndex);
|
||||
replies[childThread.thread.mMeta.mMsgId] = childThread;
|
||||
const childTs = childThread.thread.mMeta.mMostRecentTsInThread || 0;
|
||||
if (childTs > meta.mMostRecentTsInThread) meta.mMostRecentTsInThread = childTs;
|
||||
});
|
||||
}
|
||||
|
||||
return threadStruct;
|
||||
};
|
||||
|
||||
const topThread = EntryToThread(topIndex);
|
||||
Data.ParentThreads[keyid][topThread.thread.mMeta.mMsgId] = topThread.thread.mMeta;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
m.redraw();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[RS] Error updating forum display for:', keyid, e);
|
||||
} finally {
|
||||
Data.loading.delete(keyid);
|
||||
m.redraw(); // Final redraw just in case
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the body (mMsg) of a single forum post on demand.
|
||||
* Returns a Promise that resolves to the body string, or null on failure.
|
||||
*/
|
||||
async function loadPostContent(forumId, msgId) {
|
||||
// If body is already loaded, return it immediately
|
||||
if (
|
||||
Data.Threads[forumId] &&
|
||||
Data.Threads[forumId][msgId] &&
|
||||
Data.Threads[forumId][msgId].thread.mMsg !== null
|
||||
) {
|
||||
return Data.Threads[forumId][msgId].thread.mMsg;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await rs.rsJsonApiRequest('/rsgxsforums/getForumContent', {
|
||||
forumId: forumId,
|
||||
msgsIds: [msgId],
|
||||
});
|
||||
if (res && res.body && res.body.retval && res.body.msgs && res.body.msgs.length > 0) {
|
||||
const body = res.body.msgs[0].mMsg;
|
||||
// Cache the body in the existing thread entry
|
||||
if (Data.Threads[forumId] && Data.Threads[forumId][msgId]) {
|
||||
Data.Threads[forumId][msgId].thread.mMsg = body;
|
||||
}
|
||||
m.redraw();
|
||||
return body;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[RS] Error loading post content:', forumId, msgId, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const DisplayForumsFromList = () => {
|
||||
return {
|
||||
view: (v) =>
|
||||
@ -114,7 +204,7 @@ const ForumSummary = () => {
|
||||
keyid = v.attrs.details.mGroupId;
|
||||
updatedisplayforums(keyid);
|
||||
},
|
||||
view: (v) => {},
|
||||
view: (v) => { },
|
||||
};
|
||||
};
|
||||
|
||||
@ -125,26 +215,18 @@ const ForumTable = () => {
|
||||
};
|
||||
const ThreadsTable = () => {
|
||||
return {
|
||||
oninit: (v) => {},
|
||||
oninit: (v) => { },
|
||||
view: (v) =>
|
||||
m('table.threads', [
|
||||
m('tr', [m('th', 'Comment'), m('th', 'Date'), m('th', 'Author')]),
|
||||
v.children,
|
||||
]),
|
||||
};
|
||||
};
|
||||
const ThreadsReplyTable = () => {
|
||||
return {
|
||||
oninit: (v) => {},
|
||||
oninit: (v) => { },
|
||||
view: (v) =>
|
||||
m('table.threadreply', [
|
||||
m('tr', [
|
||||
m('th', ''),
|
||||
m('th', 'Comment'),
|
||||
m('th', 'Unread'),
|
||||
m('th', 'Author'),
|
||||
m('th', 'Date'),
|
||||
]),
|
||||
v.children,
|
||||
]),
|
||||
};
|
||||
@ -197,6 +279,9 @@ module.exports = {
|
||||
ThreadsReplyTable,
|
||||
popupmessage,
|
||||
updatedisplayforums,
|
||||
loadPostContent,
|
||||
getTimestampValue,
|
||||
formatTimestamp,
|
||||
GROUP_SUBSCRIBE_ADMIN,
|
||||
GROUP_SUBSCRIBE_NOT_SUBSCRIBED,
|
||||
GROUP_SUBSCRIBE_PUBLISH,
|
||||
|
||||
@ -117,52 +117,52 @@ function confirmAddPrompt(details, cert, long) {
|
||||
|
||||
long
|
||||
? m(
|
||||
'button',
|
||||
{
|
||||
onclick: async () => {
|
||||
const res = await rs.rsJsonApiRequest('/rsPeers/loadCertificateFromString', { cert });
|
||||
if (res.body.retval) {
|
||||
widget.popupMessage([
|
||||
m('h3', 'Successful'),
|
||||
m('hr'),
|
||||
m('p', 'Successfully added friend.'),
|
||||
]);
|
||||
} else {
|
||||
widget.popupMessage([
|
||||
m('h3', 'Error'),
|
||||
m('hr'),
|
||||
m('p', 'An error occoured during adding. Friend not added.'),
|
||||
]);
|
||||
}
|
||||
},
|
||||
'button',
|
||||
{
|
||||
onclick: async () => {
|
||||
const res = await rs.rsJsonApiRequest('/rsPeers/loadCertificateFromString', { cert });
|
||||
if (res.body.retval) {
|
||||
widget.popupMessage([
|
||||
m('h3', 'Successful'),
|
||||
m('hr'),
|
||||
m('p', 'Successfully added friend.'),
|
||||
]);
|
||||
} else {
|
||||
widget.popupMessage([
|
||||
m('h3', 'Error'),
|
||||
m('hr'),
|
||||
m('p', 'An error occoured during adding. Friend not added.'),
|
||||
]);
|
||||
}
|
||||
},
|
||||
'Finish'
|
||||
)
|
||||
},
|
||||
'Finish'
|
||||
)
|
||||
: m(
|
||||
'button',
|
||||
{
|
||||
onclick: async () => {
|
||||
const res = await rs.rsJsonApiRequest('/rsPeers/addSslOnlyFriend', {
|
||||
sslId: details.id,
|
||||
pgpId: details.gpg_id,
|
||||
});
|
||||
if (res.body.retval) {
|
||||
widget.popupMessage([
|
||||
m('h3', 'Successful'),
|
||||
m('hr'),
|
||||
m('p', 'Successfully added friend.'),
|
||||
]);
|
||||
} else {
|
||||
widget.popupMessage([
|
||||
m('h3', 'Error'),
|
||||
m('hr'),
|
||||
m('p', 'An error occoured during adding. Friend not added.'),
|
||||
]);
|
||||
}
|
||||
},
|
||||
'button',
|
||||
{
|
||||
onclick: async () => {
|
||||
const res = await rs.rsJsonApiRequest('/rsPeers/addSslOnlyFriend', {
|
||||
sslId: details.id,
|
||||
pgpId: details.gpg_id,
|
||||
});
|
||||
if (res.body.retval) {
|
||||
widget.popupMessage([
|
||||
m('h3', 'Successful'),
|
||||
m('hr'),
|
||||
m('p', 'Successfully added friend.'),
|
||||
]);
|
||||
} else {
|
||||
widget.popupMessage([
|
||||
m('h3', 'Error'),
|
||||
m('hr'),
|
||||
m('p', 'An error occoured during adding. Friend not added.'),
|
||||
]);
|
||||
}
|
||||
},
|
||||
'Finish'
|
||||
),
|
||||
},
|
||||
'Finish'
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -170,7 +170,7 @@ async function addFriendFromCert(cert) {
|
||||
const res = await rs.rsJsonApiRequest('/rsPeers/parseShortInvite', { invite: cert });
|
||||
|
||||
if (res.body.retval) {
|
||||
console.log(res.body);
|
||||
// console.log(res.body);
|
||||
confirmAddPrompt(res.body.details, cert, false);
|
||||
} else {
|
||||
rs.rsJsonApiRequest('/rsPeers/loadDetailsFromStringCert', { cert }, (data) => {
|
||||
|
||||
@ -16,7 +16,7 @@ const verifyLogin = async function (uname, passwd, url, displayAuthError = true)
|
||||
rs.setKeys('', '', url, false);
|
||||
rs.logon(
|
||||
loginHeader,
|
||||
displayAuthError ? displayErrorMessage : () => {},
|
||||
displayAuthError ? displayErrorMessage : () => { },
|
||||
displayErrorMessage,
|
||||
() => {
|
||||
rs.setKeys(uname, passwd, url);
|
||||
@ -33,9 +33,9 @@ function loginComponent() {
|
||||
urlParams.get('Url') || window.location.protocol === 'file:'
|
||||
? 'http://127.0.0.1:9092'
|
||||
: window.location.protocol +
|
||||
'//' +
|
||||
window.location.host +
|
||||
window.location.pathname.replace('/index.html', '');
|
||||
'//' +
|
||||
window.location.host +
|
||||
window.location.pathname.replace('/index.html', '');
|
||||
let withOptions = false;
|
||||
|
||||
const logo = () =>
|
||||
@ -83,7 +83,13 @@ function loginComponent() {
|
||||
m('a', { onclick: () => (withOptions = !withOptions) }, `${action} options`);
|
||||
|
||||
const textError = () => m('p.error[id=error]');
|
||||
|
||||
return {
|
||||
oninit: () => {
|
||||
if (rs.loginKey.isVerified && rs.loginKey.username && rs.loginKey.passwd) {
|
||||
verifyLogin(rs.loginKey.username, rs.loginKey.passwd, rs.loginKey.url, false);
|
||||
}
|
||||
},
|
||||
view: () => {
|
||||
return m(
|
||||
'form.login-page',
|
||||
@ -91,14 +97,14 @@ function loginComponent() {
|
||||
'.login-container',
|
||||
withOptions
|
||||
? [
|
||||
logo(),
|
||||
m('.extra', [m('label', 'Username:'), m('br'), inputName()]),
|
||||
m('.extra', [m('label', 'Password:'), m('br'), inputPassword()]),
|
||||
m('.extra', [m('label', 'Url:'), m('br'), inputUrl()]),
|
||||
linkOptions('hide'),
|
||||
buttonLogin(),
|
||||
textError(),
|
||||
]
|
||||
logo(),
|
||||
m('.extra', [m('label', 'Username:'), m('br'), inputName()]),
|
||||
m('.extra', [m('label', 'Password:'), m('br'), inputPassword()]),
|
||||
m('.extra', [m('label', 'Url:'), m('br'), inputUrl()]),
|
||||
linkOptions('hide'),
|
||||
buttonLogin(),
|
||||
textError(),
|
||||
]
|
||||
: [logo(), inputPassword(), linkOptions('show'), buttonLogin(), textError()]
|
||||
)
|
||||
);
|
||||
|
||||
@ -21,33 +21,27 @@ const Messages = {
|
||||
later: [],
|
||||
load() {
|
||||
rs.rsJsonApiRequest('/rsMail/getMessageSummaries', { box: util.BOX_ALL }, (data) => {
|
||||
Messages.all = data.msgList;
|
||||
Messages.inbox = Messages.all.filter(
|
||||
(msg) => (msg.msgflags & util.RS_MSG_BOXMASK) === util.RS_MSG_INBOX
|
||||
);
|
||||
Messages.sent = Messages.all.filter(
|
||||
(msg) => (msg.msgflags & util.RS_MSG_BOXMASK) === util.RS_MSG_SENTBOX
|
||||
);
|
||||
Messages.outbox = Messages.all.filter(
|
||||
(msg) => (msg.msgflags & util.RS_MSG_BOXMASK) === util.RS_MSG_OUTBOX
|
||||
);
|
||||
Messages.drafts = Messages.all.filter(
|
||||
(msg) => (msg.msgflags & util.RS_MSG_BOXMASK) === util.RS_MSG_DRAFTBOX
|
||||
);
|
||||
Messages.trash = Messages.all.filter((msg) => msg.msgflags & util.RS_MSG_TRASH);
|
||||
Messages.starred = Messages.all.filter((msg) => msg.msgflags & util.RS_MSG_STAR);
|
||||
Messages.system = Messages.all.filter((msg) => msg.msgflags & util.RS_MSG_SYSTEM);
|
||||
Messages.spam = Messages.all.filter((msg) => msg.msgflags & util.RS_MSG_SPAM);
|
||||
if (data && data.msgList) {
|
||||
Messages.all = data.msgList;
|
||||
Messages.inbox = Messages.all.filter(
|
||||
(msg) => (msg.msgflags & util.RS_MSG_BOXMASK) === util.RS_MSG_INBOX
|
||||
);
|
||||
Messages.sent = Messages.all.filter(
|
||||
(msg) => (msg.msgflags & util.RS_MSG_BOXMASK) === util.RS_MSG_SENTBOX
|
||||
);
|
||||
Messages.outbox = Messages.all.filter(
|
||||
(msg) => (msg.msgflags & util.RS_MSG_BOXMASK) === util.RS_MSG_OUTBOX
|
||||
);
|
||||
Messages.drafts = Messages.all.filter(
|
||||
(msg) => (msg.msgflags & util.RS_MSG_BOXMASK) === util.RS_MSG_DRAFTBOX
|
||||
);
|
||||
Messages.trash = Messages.all.filter((msg) => msg.msgflags & util.RS_MSG_TRASH);
|
||||
Messages.starred = Messages.all.filter((msg) => msg.msgflags & util.RS_MSG_STAR);
|
||||
Messages.system = Messages.all.filter((msg) => msg.msgflags & util.RS_MSG_SYSTEM);
|
||||
Messages.spam = Messages.all.filter((msg) => msg.msgflags & util.RS_MSG_SPAM);
|
||||
|
||||
Messages.attachment = Messages.all.filter((msg) => msg.count);
|
||||
|
||||
// Messages.important = Messages.all.filter(
|
||||
// (msg) => msg.msgflags & util.RS_MSGTAGTYPE_IMPORTANT
|
||||
// );
|
||||
// Messages.work = Messages.all.filter((msg) => msg.msgflags & util.RS_MSGTAGTYPE_WORK);
|
||||
// Messages.personal = Messages.all.filter((msg) => msg.msgflags & util.RS_MSGTAGTYPE_PERSONAL);
|
||||
// Messages.todo = Messages.all.filter((msg) => msg.msgflags & util.RS_MSGTAGTYPE_TODO);
|
||||
// Messages.later = Messages.all.filter((msg) => msg.msgflags & util.RS_MSGTAGTYPE_LATER);
|
||||
Messages.attachment = Messages.all.filter((msg) => msg.count);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -84,22 +78,22 @@ const Layout = () => {
|
||||
oninit: () => Messages.load(),
|
||||
view: (vnode) => {
|
||||
const sectionsSize = {
|
||||
inbox: Messages.inbox.length,
|
||||
outbox: Messages.outbox.length,
|
||||
drafts: Messages.drafts.length,
|
||||
sent: Messages.sent.length,
|
||||
trash: Messages.trash.length,
|
||||
inbox: (Messages.inbox || []).length,
|
||||
outbox: (Messages.outbox || []).length,
|
||||
drafts: (Messages.drafts || []).length,
|
||||
sent: (Messages.sent || []).length,
|
||||
trash: (Messages.trash || []).length,
|
||||
};
|
||||
const sectionsQuickviewSize = {
|
||||
starred: Messages.starred.length,
|
||||
system: Messages.system.length,
|
||||
spam: Messages.spam.length,
|
||||
attachment: Messages.attachment.length,
|
||||
important: Messages.important.length,
|
||||
work: Messages.work.length,
|
||||
todo: Messages.todo.length,
|
||||
later: Messages.later.length,
|
||||
personal: Messages.personal.length,
|
||||
starred: (Messages.starred || []).length,
|
||||
system: (Messages.system || []).length,
|
||||
spam: (Messages.spam || []).length,
|
||||
attachment: (Messages.attachment || []).length,
|
||||
important: (Messages.important || []).length,
|
||||
work: (Messages.work || []).length,
|
||||
todo: (Messages.todo || []).length,
|
||||
later: (Messages.later || []).length,
|
||||
personal: (Messages.personal || []).length,
|
||||
};
|
||||
|
||||
return [
|
||||
@ -120,17 +114,17 @@ const Layout = () => {
|
||||
'.node-panel',
|
||||
m('.widget', [
|
||||
m.route.get().split('/').length < 4 &&
|
||||
m('.top-heading', [
|
||||
m(
|
||||
'select.mail-tag',
|
||||
{
|
||||
value: tagselect.showval,
|
||||
onchange: (e) => (tagselect.showval = tagselect.opts[e.target.selectedIndex]),
|
||||
},
|
||||
[tagselect.opts.map((opt) => m('option', { value: opt }, opt.toLocaleString()))]
|
||||
),
|
||||
m(util.SearchBar, { list: {} }),
|
||||
]),
|
||||
m('.top-heading', [
|
||||
m(
|
||||
'select.mail-tag',
|
||||
{
|
||||
value: tagselect.showval,
|
||||
onchange: (e) => (tagselect.showval = tagselect.opts[e.target.selectedIndex]),
|
||||
},
|
||||
[tagselect.opts.map((opt) => m('option', { value: opt }, opt.toLocaleString()))]
|
||||
),
|
||||
m(util.SearchBar, { list: {} }),
|
||||
]),
|
||||
vnode.children,
|
||||
])
|
||||
),
|
||||
@ -157,9 +151,9 @@ module.exports = {
|
||||
return m(
|
||||
Layout,
|
||||
m(sections[tab] || sectionsquickview[tab], {
|
||||
list: Messages[tab].sort((msgA, msgB) => {
|
||||
const msgADate = new Date(msgA.ts.xint64 * 1000);
|
||||
const msgBDate = new Date(msgB.ts.xint64 * 1000);
|
||||
list: (Messages[tab] || []).sort((msgA, msgB) => {
|
||||
const msgADate = new Date((msgA.ts.xint64 || 0) * 1000);
|
||||
const msgBDate = new Date((msgB.ts.xint64 || 0) * 1000);
|
||||
return msgADate < msgBDate;
|
||||
}),
|
||||
})
|
||||
|
||||
@ -128,7 +128,7 @@ const AttachmentSection = () => {
|
||||
m('i.fas.fa-file-medical'),
|
||||
m('h3', `File is ${status.retval ? 'being' : 'already'} downloaded!`),
|
||||
])
|
||||
).catch((error) => console.log('error: ', error));
|
||||
).catch((error) => { });
|
||||
}
|
||||
return {
|
||||
view: (v) =>
|
||||
@ -251,69 +251,69 @@ const MessageView = () => {
|
||||
m('h3', MailData.subject),
|
||||
m('.msg-details', [
|
||||
MailData.sender &&
|
||||
m(peopleUtil.UserAvatar, {
|
||||
avatar: MailData.avatar,
|
||||
firstLetter: rs.userList.userMap[MailData.sender._addr_string]
|
||||
? rs.userList.userMap[MailData.sender._addr_string].slice(0, 1).toUpperCase()
|
||||
: '',
|
||||
}),
|
||||
m(peopleUtil.UserAvatar, {
|
||||
avatar: MailData.avatar,
|
||||
firstLetter: rs.userList.userMap[MailData.sender._addr_string]
|
||||
? rs.userList.userMap[MailData.sender._addr_string].slice(0, 1).toUpperCase()
|
||||
: '',
|
||||
}),
|
||||
m('.msg-details__info', [
|
||||
MailData.sender &&
|
||||
m('.msg-details__info-item', [
|
||||
m('b', 'From: '),
|
||||
rs.userList.userMap[MailData.sender._addr_string] || 'Unknown',
|
||||
]),
|
||||
m('.msg-details__info-item', [
|
||||
m('b', 'From: '),
|
||||
rs.userList.userMap[MailData.sender._addr_string] || 'Unknown',
|
||||
]),
|
||||
m('.msg-details__info-item', [
|
||||
m('b', 'To: '),
|
||||
MailData.toList && Object.keys(MailData.toList).length > 0
|
||||
? [
|
||||
m('#truncate.truncated-view', [
|
||||
Object.keys(MailData.toList).map((key, index) =>
|
||||
m('span', { key: index }, `${rs.userList.userMap[key] || 'Unknown'}, `)
|
||||
),
|
||||
]),
|
||||
m(
|
||||
'button.toggle-truncate',
|
||||
{
|
||||
style: {
|
||||
display: Object.keys(MailData.toList).length > 10 ? 'block' : 'none',
|
||||
},
|
||||
onclick: () => {
|
||||
document
|
||||
.querySelector('#truncate')
|
||||
.classList.toggle('truncated-view');
|
||||
},
|
||||
},
|
||||
'...'
|
||||
m('#truncate.truncated-view', [
|
||||
Object.keys(MailData.toList).map((key, index) =>
|
||||
m('span', { key: index }, `${rs.userList.userMap[key] || 'Unknown'}, `)
|
||||
),
|
||||
]
|
||||
]),
|
||||
m(
|
||||
'button.toggle-truncate',
|
||||
{
|
||||
style: {
|
||||
display: Object.keys(MailData.toList).length > 10 ? 'block' : 'none',
|
||||
},
|
||||
onclick: () => {
|
||||
document
|
||||
.querySelector('#truncate')
|
||||
.classList.toggle('truncated-view');
|
||||
},
|
||||
},
|
||||
'...'
|
||||
),
|
||||
]
|
||||
: m('span', 'Unknown'),
|
||||
]),
|
||||
MailData.ccList &&
|
||||
Object.keys(MailData.ccList).length > 0 &&
|
||||
m('.msg-details__info-item', [
|
||||
m('b', 'Cc: '),
|
||||
Object.keys(MailData.ccList).map((key, index) =>
|
||||
m('p', { key: index }, `${rs.userList.userMap[key]}, `)
|
||||
),
|
||||
]),
|
||||
Object.keys(MailData.ccList).length > 0 &&
|
||||
m('.msg-details__info-item', [
|
||||
m('b', 'Cc: '),
|
||||
Object.keys(MailData.ccList).map((key, index) =>
|
||||
m('p', { key: index }, `${rs.userList.userMap[key]}, `)
|
||||
),
|
||||
]),
|
||||
MailData.bccList &&
|
||||
Object.keys(MailData.bccList).length > 0 &&
|
||||
m('.msg-details__info-item', [
|
||||
m('b', 'Bcc: '),
|
||||
Object.keys(MailData.bccList).map((key, index) =>
|
||||
m('p', { key: index }, `${rs.userList.userMap[key]}, `)
|
||||
),
|
||||
]),
|
||||
Object.keys(MailData.bccList).length > 0 &&
|
||||
m('.msg-details__info-item', [
|
||||
m('b', 'Bcc: '),
|
||||
Object.keys(MailData.bccList).map((key, index) =>
|
||||
m('p', { key: index }, `${rs.userList.userMap[key]}, `)
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
m('.msg-view__body', m('#msgView')),
|
||||
MailData.files.length > 0 &&
|
||||
m('.msg-view__attachment', [
|
||||
m('h3', 'Attachments'),
|
||||
m('.msg-view__attachment-items', m(AttachmentSection, { files: MailData.files })),
|
||||
]),
|
||||
m('.msg-view__attachment', [
|
||||
m('h3', 'Attachments'),
|
||||
m('.msg-view__attachment-items', m(AttachmentSection, { files: MailData.files })),
|
||||
]),
|
||||
],
|
||||
m(
|
||||
'.composePopupOverlay#mailComposerPopup',
|
||||
@ -322,14 +322,14 @@ const MessageView = () => {
|
||||
'.composePopup',
|
||||
MailData.sender._addr_string
|
||||
? m(compose, {
|
||||
msgType: 'reply',
|
||||
senderId: MailData.sender._addr_string,
|
||||
recipientList: MailData.toList,
|
||||
subject: MailData.subject,
|
||||
replyMessage: MailData.message,
|
||||
timeStamp: new Date(MailData.timeStamp * 1000),
|
||||
setShowCompose,
|
||||
})
|
||||
msgType: 'reply',
|
||||
senderId: MailData.sender._addr_string,
|
||||
recipientList: MailData.toList,
|
||||
subject: MailData.subject,
|
||||
replyMessage: MailData.message,
|
||||
timeStamp: new Date(MailData.timeStamp * 1000),
|
||||
setShowCompose,
|
||||
})
|
||||
: m('.widget', m('.widget__heading', m('h3', 'Sender is not known'))),
|
||||
m('button.red.close-btn', { onclick: () => setShowCompose(false) }, m('i.fas.fa-times'))
|
||||
)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
const m = require('mithril');
|
||||
|
||||
const login = require('login');
|
||||
const rs = require('rswebui');
|
||||
const home = require('home');
|
||||
const network = require('network/network');
|
||||
const people = require('people/people_resolver');
|
||||
@ -36,24 +37,65 @@ const navbar = () => {
|
||||
},
|
||||
[
|
||||
m('.nav-menu__logo', [
|
||||
m('img', {
|
||||
src: 'images/retroshare.svg',
|
||||
alt: 'retroshare_icon',
|
||||
}),
|
||||
m('h5', 'Retroshare'),
|
||||
m(
|
||||
'.logo-container',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
marginRight: '10px',
|
||||
},
|
||||
},
|
||||
[
|
||||
m('img', {
|
||||
src: 'images/retroshare.svg',
|
||||
alt: 'retroshare_icon',
|
||||
}),
|
||||
m('i.fas.fa-circle', {
|
||||
style: {
|
||||
color: rs.connectionState.status ? '#2ecc71' : '#e74c3c',
|
||||
fontSize: '0.6em',
|
||||
marginTop: '5px',
|
||||
transition: 'color 0.3s ease',
|
||||
},
|
||||
title: rs.connectionState.status ? 'Connected to RetroShare Core' : 'Connection Lost',
|
||||
}),
|
||||
m('span.webui-version', { style: { fontSize: '0.7em', marginTop: '3px', color: '#888' } }, 'v131'),
|
||||
m('i.fas.fa-sync-alt.refresh-icon', {
|
||||
style: { fontSize: '0.8em', marginTop: '2px', cursor: 'pointer', color: '#888' },
|
||||
onclick: () => window.location.reload(true),
|
||||
title: 'Force reload application',
|
||||
}),
|
||||
]
|
||||
),
|
||||
m('.nav-menu__logo-text', [
|
||||
m('h5', 'RetroShare'),
|
||||
]),
|
||||
]),
|
||||
m('.nav-menu__box', [
|
||||
Object.keys(vnode.attrs.links).map((linkName, i) => {
|
||||
Object.keys(vnode.attrs.links).map((linkName) => {
|
||||
const active = m.route.get().split('/')[1] === linkName;
|
||||
return m(
|
||||
m.route.Link,
|
||||
{
|
||||
href: vnode.attrs.links[linkName],
|
||||
class: 'item' + (active ? ' item-selected' : ''),
|
||||
class: (active ? 'active-link' : '') + ' item',
|
||||
},
|
||||
[navIcon[linkName], m('p', linkName)]
|
||||
[
|
||||
navIcon[linkName],
|
||||
m('span', linkName.charAt(0).toUpperCase() + linkName.slice(1)),
|
||||
]
|
||||
);
|
||||
}),
|
||||
m(
|
||||
'a.logout-link.item',
|
||||
{
|
||||
onclick: () => rs.logout(),
|
||||
style: { marginTop: 'auto', cursor: 'pointer' },
|
||||
},
|
||||
[m('i.fas.fa-sign-out-alt'), m('span', 'Logout')]
|
||||
),
|
||||
m(
|
||||
'button.toggle-nav',
|
||||
{
|
||||
@ -157,3 +199,13 @@ m.route(document.getElementById('main'), '/', {
|
||||
render: (v) => m(Layout, m(config, v.attrs)),
|
||||
},
|
||||
});
|
||||
|
||||
// v51 architectural fix: ensure event queue starts on direct route refresh
|
||||
if (rs.loginKey.isVerified && rs.loginKey.username && rs.loginKey.passwd) {
|
||||
rs.logon(
|
||||
{ Authorization: `Basic ${btoa(`${rs.loginKey.username}:${rs.loginKey.passwd}`)}` },
|
||||
() => { }, // displayAuthError
|
||||
() => { }, // displayErrorMessage
|
||||
() => { }
|
||||
);
|
||||
}
|
||||
|
||||
@ -24,31 +24,28 @@ const SignedIdentiy = () => {
|
||||
style: 'margin-top:160px;',
|
||||
onclick: () => {
|
||||
rs.rsJsonApiRequest('/rsIdentity/getOwnSignedIds', {}, (owns) => {
|
||||
console.log(owns.ids[0]);
|
||||
console.log(v.attrs.name);
|
||||
|
||||
owns.ids.length > 0
|
||||
? rs.rsJsonApiRequest(
|
||||
'/rsIdentity/createIdentity',
|
||||
{
|
||||
id: owns.ids[0],
|
||||
name: v.attrs.name,
|
||||
pseudonimous: false,
|
||||
pgpPassword: passphase,
|
||||
},
|
||||
(data) => {
|
||||
const message = data.retval
|
||||
? 'Successfully created identity.'
|
||||
: 'An error occured while creating identity.';
|
||||
console.log(message);
|
||||
widget.popupMessage([m('h3', 'Create new Identity'), m('hr'), message]);
|
||||
}
|
||||
)
|
||||
'/rsIdentity/createIdentity',
|
||||
{
|
||||
id: owns.ids[0],
|
||||
name: v.attrs.name,
|
||||
pseudonimous: false,
|
||||
pgpPassword: passphase,
|
||||
},
|
||||
(data) => {
|
||||
const message = data.retval
|
||||
? 'Successfully created identity.'
|
||||
: 'An error occured while creating identity.';
|
||||
widget.popupMessage([m('h3', 'Create new Identity'), m('hr'), message]);
|
||||
}
|
||||
)
|
||||
: widget.popupMessage([
|
||||
m('h3', 'Create new Identity'),
|
||||
m('hr'),
|
||||
'An error occured while creating identity.',
|
||||
]);
|
||||
m('h3', 'Create new Identity'),
|
||||
m('hr'),
|
||||
'An error occured while creating identity.',
|
||||
]);
|
||||
});
|
||||
},
|
||||
},
|
||||
@ -84,7 +81,6 @@ const CreateIdentity = () => {
|
||||
style: 'border:1px solid black',
|
||||
oninput: (e) => {
|
||||
pseudonimous = e.target.value === 'true';
|
||||
console.log(pseudonimous);
|
||||
},
|
||||
},
|
||||
[
|
||||
@ -99,10 +95,10 @@ const CreateIdentity = () => {
|
||||
m(
|
||||
'p',
|
||||
'You can have one or more identities. ' +
|
||||
'They are used when you chat in lobbies, ' +
|
||||
'forums and channel comments. ' +
|
||||
'They act as the destination for distant chat and ' +
|
||||
'the Retroshare distant mail system.'
|
||||
'They are used when you chat in lobbies, ' +
|
||||
'forums and channel comments. ' +
|
||||
'They act as the destination for distant chat and ' +
|
||||
'the Retroshare distant mail system.'
|
||||
),
|
||||
m(
|
||||
'button',
|
||||
@ -111,18 +107,18 @@ const CreateIdentity = () => {
|
||||
!pseudonimous
|
||||
? widget.popupMessage(m(SignedIdentiy, { name }))
|
||||
: rs.rsJsonApiRequest(
|
||||
'/rsIdentity/createIdentity',
|
||||
{
|
||||
name,
|
||||
pseudonimous,
|
||||
},
|
||||
(data) => {
|
||||
const message = data.retval
|
||||
? 'Successfully created identity.'
|
||||
: 'An error occured while creating identity.';
|
||||
widget.popupMessage([m('h3', 'Create new Identity'), m('hr'), message]);
|
||||
}
|
||||
);
|
||||
'/rsIdentity/createIdentity',
|
||||
{
|
||||
name,
|
||||
pseudonimous,
|
||||
},
|
||||
(data) => {
|
||||
const message = data.retval
|
||||
? 'Successfully created identity.'
|
||||
: 'An error occured while creating identity.';
|
||||
widget.popupMessage([m('h3', 'Create new Identity'), m('hr'), message]);
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
'Create'
|
||||
@ -192,28 +188,28 @@ const EditIdentity = () => {
|
||||
onclick: () => {
|
||||
!peopleUtil.checksudo(v.attrs.details.mPgpId)
|
||||
? widget.popupMessage([
|
||||
m(SignedEditIdentity, {
|
||||
name,
|
||||
details: v.attrs.details,
|
||||
}),
|
||||
])
|
||||
m(SignedEditIdentity, {
|
||||
name,
|
||||
details: v.attrs.details,
|
||||
}),
|
||||
])
|
||||
: rs.rsJsonApiRequest(
|
||||
'/rsIdentity/updateIdentity',
|
||||
{
|
||||
id: v.attrs.details.mId,
|
||||
'/rsIdentity/updateIdentity',
|
||||
{
|
||||
id: v.attrs.details.mId,
|
||||
|
||||
name,
|
||||
name,
|
||||
|
||||
// avatar: v.attrs.details.mAvatar.mData.base64,
|
||||
pseudonimous: true,
|
||||
},
|
||||
(data) => {
|
||||
const message = data.retval
|
||||
? 'Successfully Updated identity.'
|
||||
: 'An error occured while updating identity.';
|
||||
widget.popupMessage([m('h3', 'Update Identity'), m('hr'), message]);
|
||||
}
|
||||
);
|
||||
// avatar: v.attrs.details.mAvatar.mData.base64,
|
||||
pseudonimous: true,
|
||||
},
|
||||
(data) => {
|
||||
const message = data.retval
|
||||
? 'Successfully Updated identity.'
|
||||
: 'An error occured while updating identity.';
|
||||
widget.popupMessage([m('h3', 'Update Identity'), m('hr'), message]);
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
'Save'
|
||||
@ -277,10 +273,10 @@ const Identity = () => {
|
||||
[
|
||||
m('h4', details.mNickname),
|
||||
details.mNickname &&
|
||||
m(peopleUtil.UserAvatar, {
|
||||
avatar: details.mAvatar,
|
||||
firstLetter: details.mNickname.slice(0, 1).toUpperCase(),
|
||||
}),
|
||||
m(peopleUtil.UserAvatar, {
|
||||
avatar: details.mAvatar,
|
||||
firstLetter: details.mNickname.slice(0, 1).toUpperCase(),
|
||||
}),
|
||||
m('.details', [
|
||||
m('p', 'ID:'),
|
||||
m('p', details.mId),
|
||||
@ -303,6 +299,16 @@ const Identity = () => {
|
||||
: 'undefiend'
|
||||
),
|
||||
]),
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
onclick: () =>
|
||||
m.route.set('/chat/:userid/createdistantchat', {
|
||||
userid: details.mId,
|
||||
}),
|
||||
},
|
||||
'Chat'
|
||||
),
|
||||
m(
|
||||
'button',
|
||||
{
|
||||
|
||||
@ -10,33 +10,28 @@ const UserAvatar = () => ({
|
||||
const imageURI = v.attrs.avatar;
|
||||
return imageURI === undefined || imageURI.mData.base64 === ''
|
||||
? m(
|
||||
'div.defaultAvatar',
|
||||
{
|
||||
// image isn't getting loaded
|
||||
// ? m('img.defaultAvatar', {
|
||||
// src: '../data/user.png'
|
||||
// })
|
||||
},
|
||||
m('p', v.attrs.firstLetter)
|
||||
)
|
||||
'div.defaultAvatar',
|
||||
{
|
||||
// image isn't getting loaded
|
||||
// ? m('img.defaultAvatar', {
|
||||
// src: '../data/user.png'
|
||||
// })
|
||||
},
|
||||
m('p', v.attrs.firstLetter)
|
||||
)
|
||||
: m('img.avatar', {
|
||||
src: 'data:image/png;base64,' + imageURI.mData.base64,
|
||||
});
|
||||
src: 'data:image/png;base64,' + imageURI.mData.base64,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function contactlist(list) {
|
||||
const result = [];
|
||||
if (list !== undefined) {
|
||||
list.map((id) => {
|
||||
id.isSearched = true;
|
||||
rs.rsJsonApiRequest('/rsIdentity/isARegularContact', { id: id.mGroupId }, (data) => {
|
||||
if (data.retval) result.push(id);
|
||||
console.log(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
return result;
|
||||
if (list === undefined) return [];
|
||||
return list.filter((id) => {
|
||||
id.isSearched = true;
|
||||
const entry = rs.userList.userMap[id.mGroupId];
|
||||
return entry && entry.isContact;
|
||||
});
|
||||
}
|
||||
|
||||
function sortUsers(list) {
|
||||
@ -63,7 +58,7 @@ function sortIds(list) {
|
||||
return list;
|
||||
}
|
||||
|
||||
async function ownIds(consumer = () => {}, onlySigned = false) {
|
||||
async function ownIds(consumer = () => { }, onlySigned = false) {
|
||||
await rs.rsJsonApiRequest('/rsIdentity/getOwnSignedIds', {}, (owns) => {
|
||||
if (onlySigned) {
|
||||
consumer(sortIds(owns.ids));
|
||||
@ -122,10 +117,10 @@ const regularcontactInfo = () => {
|
||||
[
|
||||
m('h4', details.mNickname),
|
||||
details.mNickname &&
|
||||
m(UserAvatar, {
|
||||
avatar: details.mAvatar,
|
||||
firstLetter: details.mNickname.slice(0, 1).toUpperCase(),
|
||||
}),
|
||||
m(UserAvatar, {
|
||||
avatar: details.mAvatar,
|
||||
firstLetter: details.mNickname.slice(0, 1).toUpperCase(),
|
||||
}),
|
||||
m('.details', [
|
||||
m('p', 'ID:'),
|
||||
m('p', details.mId),
|
||||
|
||||
@ -68,10 +68,10 @@ const RsEventsType = {
|
||||
|
||||
const API_URL = 'http://127.0.0.1:9092';
|
||||
const loginKey = {
|
||||
username: '',
|
||||
passwd: '',
|
||||
isVerified: false,
|
||||
url: API_URL,
|
||||
username: sessionStorage.getItem('rs_username') || '',
|
||||
passwd: sessionStorage.getItem('rs_passwd') || '',
|
||||
isVerified: sessionStorage.getItem('rs_isVerified') === 'true',
|
||||
url: sessionStorage.getItem('rs_url') || API_URL,
|
||||
};
|
||||
|
||||
// Make this as object property?
|
||||
@ -80,12 +80,30 @@ function setKeys(username, password, url = API_URL, verified = true) {
|
||||
loginKey.passwd = password;
|
||||
loginKey.url = url;
|
||||
loginKey.isVerified = verified;
|
||||
|
||||
if (verified) {
|
||||
sessionStorage.setItem('rs_username', username);
|
||||
sessionStorage.setItem('rs_passwd', password);
|
||||
sessionStorage.setItem('rs_url', url);
|
||||
sessionStorage.setItem('rs_isVerified', 'true');
|
||||
} else {
|
||||
sessionStorage.removeItem('rs_isVerified');
|
||||
}
|
||||
}
|
||||
|
||||
function logout() {
|
||||
setKeys('', '', loginKey.url, false);
|
||||
m.route.set('/');
|
||||
}
|
||||
|
||||
const connectionState = {
|
||||
status: true,
|
||||
};
|
||||
|
||||
function rsJsonApiRequest(
|
||||
path,
|
||||
data = {},
|
||||
callback = () => {},
|
||||
callback = () => { },
|
||||
async = true,
|
||||
headers = {},
|
||||
handleDeserialize = JSON.parse,
|
||||
@ -94,7 +112,9 @@ function rsJsonApiRequest(
|
||||
) {
|
||||
headers['Accept'] = 'application/json';
|
||||
if (loginKey.isVerified) {
|
||||
headers['Authorization'] = 'Basic ' + btoa(loginKey.username + ':' + loginKey.passwd);
|
||||
if (loginKey.username && loginKey.passwd) {
|
||||
headers['Authorization'] = 'Basic ' + btoa(loginKey.username + ':' + loginKey.passwd);
|
||||
}
|
||||
}
|
||||
// NOTE: After upgrading to mithrilv2, options.extract is no longer required
|
||||
// since the status will become part of return value and then
|
||||
@ -117,23 +137,42 @@ function rsJsonApiRequest(
|
||||
headers,
|
||||
body: data,
|
||||
|
||||
config,
|
||||
xhr: config,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.status === 200) {
|
||||
callback(result.body, true);
|
||||
connectionState.status = true;
|
||||
try {
|
||||
callback(result.body, true);
|
||||
} catch (e) {
|
||||
console.error('[RS] Error in success callback for path:', path, e);
|
||||
}
|
||||
} else {
|
||||
if(result.status === 403 || result.status === 401)
|
||||
loginKey.isVerified = false;
|
||||
callback(result, false);
|
||||
m.route.set('/');
|
||||
connectionState.status = false;
|
||||
if (result.status === 401 || result.status === 403) {
|
||||
setKeys(loginKey.username, loginKey.passwd, loginKey.url, false);
|
||||
m.route.set('/');
|
||||
} else if (result.status === 0) {
|
||||
console.error('[RS] Retroshare-jsonapi not available.');
|
||||
} else {
|
||||
console.error('[RS] HTTP error:', result.status, result.statusText);
|
||||
}
|
||||
try {
|
||||
callback(result, false);
|
||||
} catch (e) {
|
||||
console.error('[RS] Error in error callback for path:', path, e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.catch(function (e) {
|
||||
callback(e, false);
|
||||
console.error('Error: While sending request for path:', path, '\ninfo:', e);
|
||||
m.route.set('/');
|
||||
connectionState.status = false;
|
||||
try {
|
||||
callback(e, false);
|
||||
} catch (cbErr) {
|
||||
// console.error('[RS] Error in catch callback for path:', path, cbErr);
|
||||
}
|
||||
console.error('[RS] Error: While sending request for path:', path, '\ninfo:', e);
|
||||
});
|
||||
}
|
||||
|
||||
@ -176,10 +215,10 @@ const eventQueue = {
|
||||
// #define RS_CHAT_TYPE_PUBLIC 1
|
||||
// #define RS_CHAT_TYPE_PRIVATE 2
|
||||
|
||||
2: (chatId) => chatId.distant_chat_id, // distant chat (initiate? -> todo accept)
|
||||
// #define RS_CHAT_TYPE_LOBBY 3
|
||||
3: (chatId) => chatId.lobby_id.xstr64, // lobby_id
|
||||
// #define RS_CHAT_TYPE_DISTANT 4
|
||||
1: (cid) => hexId(cid),
|
||||
2: (cid) => hexId(cid),
|
||||
3: (cid) => hexId(cid),
|
||||
4: (cid) => hexId(cid),
|
||||
},
|
||||
messages: {},
|
||||
chatMessages: (chatId, owner, action) => {
|
||||
@ -195,25 +234,36 @@ const eventQueue = {
|
||||
)
|
||||
)
|
||||
) {
|
||||
console.info('unknown chat event', chatId);
|
||||
if (chatId) {
|
||||
// Silent match
|
||||
}
|
||||
}
|
||||
},
|
||||
handler: (event, owner) =>
|
||||
owner.chatMessages(event.mChatMessage.chat_id, owner, (r) => {
|
||||
console.info('adding chat', r, event.mChatMessage);
|
||||
r.push(event.mChatMessage);
|
||||
owner.notify(event.mChatMessage);
|
||||
}),
|
||||
notify: () => {},
|
||||
handler: (event, owner) => {
|
||||
if (event && event.mChatMessage && event.mChatMessage.chat_id) {
|
||||
owner.chatMessages(event.mChatMessage.chat_id, owner, (r) => {
|
||||
r.push(event.mChatMessage);
|
||||
owner.notify(event.mChatMessage);
|
||||
});
|
||||
} else if (event && event.mCid) {
|
||||
// Administrative chat event (e.g. lobby info change, peer join/leave)
|
||||
// Silent for now to avoid console spam, as actual messages use mChatMessage
|
||||
}
|
||||
},
|
||||
notify: () => { },
|
||||
},
|
||||
[RsEventsType.GXS_CIRCLES]: {
|
||||
// Circles (ignore in the meantime)
|
||||
handler: (event, owner) => {},
|
||||
handler: (event, owner) => { },
|
||||
},
|
||||
[RsEventsType.SHARED_DIRECTORIES]: {
|
||||
// Deprecated/Administrative (ignore quietly)
|
||||
handler: (event, owner) => { },
|
||||
},
|
||||
},
|
||||
handler: (event) => {
|
||||
if (!deeperIfExist(eventQueue.events, event.mType, (owner) => owner.handler(event, owner))) {
|
||||
console.info('unhandled event', event);
|
||||
// Ignore unhandled events silently
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -221,20 +271,71 @@ const eventQueue = {
|
||||
const userList = {
|
||||
users: [],
|
||||
userMap: {},
|
||||
pendingIds: new Set(),
|
||||
fetchTimer: null,
|
||||
|
||||
triggerFetch: () => {
|
||||
if (userList.fetchTimer) return;
|
||||
userList.fetchTimer = setTimeout(() => {
|
||||
userList.fetchTimer = null;
|
||||
if (userList.pendingIds.size === 0) return;
|
||||
|
||||
const ids = Array.from(userList.pendingIds);
|
||||
userList.pendingIds.clear();
|
||||
|
||||
userList.fetchBulk(ids);
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
fetchBulk: (ids) => {
|
||||
// Chunk requests to avoid too large payloads if necessary, but for now 100 is safe
|
||||
const chunkSize = 100;
|
||||
for (let i = 0; i < ids.length; i += chunkSize) {
|
||||
const chunk = ids.slice(i, i + chunkSize);
|
||||
rsJsonApiRequest('/rsIdentity/getIdentitiesInfo', { ids: chunk }, (data, success) => {
|
||||
if (success && data.idsInfo) {
|
||||
data.idsInfo.forEach((info) => {
|
||||
const gid = info.mMeta && info.mMeta.mGroupId;
|
||||
if (gid) {
|
||||
userList.userMap[gid] = {
|
||||
name: info.mMeta.mGroupName,
|
||||
isContact: info.mIsAContact,
|
||||
};
|
||||
}
|
||||
});
|
||||
m.redraw();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
loadUsers: () => {
|
||||
rsJsonApiRequest('/rsIdentity/getIdentitiesSummaries', {}, (list) => {
|
||||
if (list !== undefined) {
|
||||
console.info('loading ' + list.ids.length + ' users ...');
|
||||
if (list !== undefined && list.ids) {
|
||||
userList.users = list.ids;
|
||||
userList.userMap = list.ids.reduce((a, c) => {
|
||||
a[c.mGroupId] = c.mGroupName;
|
||||
a[c.mGroupId] = { name: c.mGroupName, isContact: false };
|
||||
return a;
|
||||
}, {});
|
||||
|
||||
// Fetch contact status and details in bulk immediately
|
||||
userList.fetchBulk(list.ids.map((u) => u.mGroupId));
|
||||
}
|
||||
});
|
||||
},
|
||||
username: (id) => {
|
||||
return userList.userMap[id] || id;
|
||||
if (!id) return '';
|
||||
const entry = userList.userMap[id];
|
||||
const name = typeof entry === 'object' ? entry.name : entry;
|
||||
|
||||
if (!name && id.length > 10) {
|
||||
if (!userList.pendingIds.has(id)) {
|
||||
userList.pendingIds.add(id);
|
||||
userList.triggerFetch();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
return name || id;
|
||||
},
|
||||
};
|
||||
|
||||
@ -251,70 +352,96 @@ const userList = {
|
||||
function startEventQueue(
|
||||
info,
|
||||
loginHeader = {},
|
||||
displayAuthError = () => {},
|
||||
displayErrorMessage = () => {},
|
||||
successful = () => {}
|
||||
displayAuthError = () => { },
|
||||
displayErrorMessage = () => { },
|
||||
successful = () => { }
|
||||
) {
|
||||
return rsJsonApiRequest(
|
||||
'/rsEvents/registerEventsHandler',
|
||||
{},
|
||||
(data, success) => {
|
||||
if (success) {
|
||||
// unused
|
||||
} else if (data.status === 401) {
|
||||
const xhr = new window.XMLHttpRequest();
|
||||
let lastIndex = 0;
|
||||
xhr.open('POST', loginKey.url + '/rsEvents/registerEventsHandler', true);
|
||||
|
||||
// Set headers for authentication
|
||||
const headers = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
...loginHeader,
|
||||
};
|
||||
|
||||
if (loginKey.isVerified && !headers['Authorization']) {
|
||||
if (loginKey.username && loginKey.passwd) {
|
||||
headers['Authorization'] = 'Basic ' + btoa(loginKey.username + ':' + loginKey.passwd);
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(headers).forEach((key) => {
|
||||
xhr.setRequestHeader(key, headers[key]);
|
||||
});
|
||||
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 401) {
|
||||
displayAuthError('Incorrect login/password.');
|
||||
} else if (data.status === 0) {
|
||||
displayErrorMessage([
|
||||
'Retroshare-jsonapi not available.',
|
||||
m('br'),
|
||||
'Please fix host and/or port.',
|
||||
]);
|
||||
} else {
|
||||
displayErrorMessage('Login failed: HTTP ' + data.status + ' ' + data.statusText);
|
||||
}
|
||||
},
|
||||
true,
|
||||
loginHeader,
|
||||
JSON.parse,
|
||||
JSON.stringify,
|
||||
(xhr, args, url) => {
|
||||
let lastIndex = 0;
|
||||
xhr.onprogress = (ev) => {
|
||||
const currIndex = xhr.responseText.length;
|
||||
if (currIndex > lastIndex) {
|
||||
const parts = xhr.responseText.substring(lastIndex, currIndex);
|
||||
lastIndex = currIndex;
|
||||
parts
|
||||
.trim()
|
||||
.split('\n\n')
|
||||
.filter((e) => e.startsWith('data: {'))
|
||||
.map((e) => e.substr(6))
|
||||
.map(JSON.parse)
|
||||
.forEach((data) => {
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onprogress = (ev) => {
|
||||
const currIndex = xhr.responseText.length;
|
||||
if (currIndex > lastIndex) {
|
||||
const parts = xhr.responseText.substring(lastIndex, currIndex);
|
||||
lastIndex = currIndex;
|
||||
parts
|
||||
.trim()
|
||||
.split('\n\n')
|
||||
.filter((e) => e.trim().length > 0)
|
||||
.forEach((e) => {
|
||||
if (e.startsWith('data: {')) {
|
||||
try {
|
||||
const data = JSON.parse(e.substr(6));
|
||||
if (Object.prototype.hasOwnProperty.call(data, 'retval')) {
|
||||
console.info(
|
||||
info + ' [' + data.retval.errorCategory + '] ' + data.retval.errorMessage
|
||||
);
|
||||
data.retval.errorNumber === 0
|
||||
? successful()
|
||||
: displayErrorMessage(
|
||||
`${info} failed: [${data.retval.errorCategory}] ${data.retval.errorMessage}`
|
||||
);
|
||||
if (data.retval.errorNumber !== 0) {
|
||||
displayErrorMessage(
|
||||
`${info} failed: [${data.retval.errorCategory}] ${data.retval.errorMessage}`
|
||||
);
|
||||
} else {
|
||||
successful();
|
||||
}
|
||||
} else if (Object.prototype.hasOwnProperty.call(data, 'event')) {
|
||||
data.event.queueSize = currIndex;
|
||||
eventQueue.handler(data.event);
|
||||
try {
|
||||
eventQueue.handler(data.event);
|
||||
} catch (err) {
|
||||
console.error('[RS] Error in event handler:', err, data.event);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (currIndex > 1e5) {
|
||||
// max 100 kB eventQueue
|
||||
startEventQueue('restart queue');
|
||||
xhr.abort();
|
||||
} catch (err) {
|
||||
console.error('[RS] JSON parse error for part:', e, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return xhr;
|
||||
});
|
||||
if (currIndex > 1e6) {
|
||||
// max 1 MB eventQueue
|
||||
startEventQueue('restart queue');
|
||||
xhr.abort();
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
xhr.onload = () => { };
|
||||
|
||||
xhr.onerror = (err) => {
|
||||
console.error('[RS] Event Queue XHR error occurred:', err);
|
||||
// Retry after 5 seconds to avoid silent event loss
|
||||
setTimeout(() => {
|
||||
console.log('[RS] Retrying event queue connection...');
|
||||
startEventQueue(info, loginHeader, displayAuthError, displayErrorMessage, successful);
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
// We need to send an eventType to registerEventsHandler
|
||||
// 0 means all events
|
||||
xhr.send(JSON.stringify({ eventType: 0 }));
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function logon(loginHeader, displayAuthError, displayErrorMessage, successful) {
|
||||
@ -333,8 +460,32 @@ function formatBytes(bytes, decimals = 2) {
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function hexId(id) {
|
||||
if (!id) return '';
|
||||
if (typeof id === 'string') return id;
|
||||
if (typeof id === 'number') return String(id);
|
||||
if (typeof id === 'object') {
|
||||
// 1. Check for xstr64 (64-bit wrapped ID)
|
||||
if (id.xstr64 && id.xstr64 !== '0') return id.xstr64;
|
||||
|
||||
// 2. Search for any hex string of appropriate length (128-bit or 64-bit)
|
||||
const keys = Object.keys(id);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const val = id[keys[i]];
|
||||
if (typeof val === 'string' && val.length >= 16 && val !== '00000000000000000000000000000000') return val;
|
||||
// Search deeper for nested xstr64
|
||||
if (val && typeof val === 'object' && val.xstr64 && val.xstr64 !== '0') return val.xstr64;
|
||||
}
|
||||
// 3. Last resort fallbacks
|
||||
if (id.xstr64 !== undefined) return String(id.xstr64);
|
||||
}
|
||||
return String(id);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
rsJsonApiRequest,
|
||||
idToHex: hexId,
|
||||
connectionState,
|
||||
setKeys,
|
||||
setBackgroundTask,
|
||||
logon,
|
||||
@ -343,4 +494,5 @@ module.exports = {
|
||||
userList,
|
||||
loginKey,
|
||||
formatBytes,
|
||||
logout,
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file contains all application-wide Sass mixins.
|
||||
// -----------------------------------------------------------------------------
|
||||
@use 'sass:color';
|
||||
@use './colors' as *;
|
||||
|
||||
/// Button Mixin
|
||||
@ -38,13 +39,17 @@
|
||||
position: relative;
|
||||
line-height: 1.2;
|
||||
|
||||
@if ($type == 'info') {
|
||||
color: transparentize($dark-color, 0.2);
|
||||
border: 1px solid transparentize($primary-retro-color, 0.2);
|
||||
} @else if ($type == 'warning') {
|
||||
border: 1px solid transparentize($golden-yellow-color, 0.2);
|
||||
} @else if ($type == 'danger') {
|
||||
border: 1px solid transparentize($red-color, 0.2);
|
||||
@if ($type =='info') {
|
||||
color: color.adjust($dark-color, $alpha: -0.2);
|
||||
border: 1px solid color.adjust($primary-retro-color, $alpha: -0.2);
|
||||
}
|
||||
|
||||
@else if ($type =='warning') {
|
||||
border: 1px solid color.adjust($golden-yellow-color, $alpha: -0.2);
|
||||
}
|
||||
|
||||
@else if ($type =='danger') {
|
||||
border: 1px solid color.adjust($red-color, $alpha: -0.2);
|
||||
}
|
||||
|
||||
&::before {
|
||||
@ -53,13 +58,17 @@
|
||||
top: 0.5rem;
|
||||
left: 0.5rem;
|
||||
|
||||
@if ($type == 'info') {
|
||||
@if ($type =='info') {
|
||||
content: '\f05a';
|
||||
color: $primary-color;
|
||||
} @else if ($type == 'warning') {
|
||||
}
|
||||
|
||||
@else if ($type =='warning') {
|
||||
content: '\f071';
|
||||
color: $golden-yellow-color;
|
||||
} @else if ($type == 'danger') {
|
||||
}
|
||||
|
||||
@else if ($type =='danger') {
|
||||
content: '\f05a';
|
||||
color: $red-color;
|
||||
}
|
||||
@ -69,15 +78,15 @@
|
||||
@mixin flex($direction: '', $justify: '', $align: '', $gap: 0) {
|
||||
display: flex;
|
||||
|
||||
@if ($direction != '') {
|
||||
@if ($direction !='') {
|
||||
flex-direction: $direction;
|
||||
}
|
||||
|
||||
@if ($justify != '') {
|
||||
@if ($justify !='') {
|
||||
justify-content: $justify;
|
||||
}
|
||||
|
||||
@if ($align != '') {
|
||||
@if ($align !='') {
|
||||
align-items: $align;
|
||||
}
|
||||
|
||||
@ -88,8 +97,7 @@
|
||||
|
||||
// Font
|
||||
@mixin fontdef-woff($FontPath, $FontName, $FontVersion: '1.0.0', $FontType: 'Regular') {
|
||||
src:
|
||||
url('#{$FontPath}/#{$FontName}-#{$FontType}.woff2') format('woff2'),
|
||||
url('#{$FontPath}/#{$FontName}-#{$FontType}.woff') format('woff'),
|
||||
url('#{$FontPath}/#{$FontName}-#{$FontType}.ttf') format('truetype');
|
||||
}
|
||||
src: url('#{$FontPath}/#{$FontName}-#{$FontType}.woff2') format('woff2'),
|
||||
url('#{$FontPath}/#{$FontName}-#{$FontType}.woff') format('woff'),
|
||||
url('#{$FontPath}/#{$FontName}-#{$FontType}.ttf') format('truetype');
|
||||
}
|
||||
@ -1,20 +1,24 @@
|
||||
@use 'sass:color';
|
||||
@use '../abstracts' as *;
|
||||
|
||||
.media-item {
|
||||
display: flex;
|
||||
margin-top: 0.5rem;
|
||||
padding: 1rem;
|
||||
border: 1px solid transparentize($dark-color, 0.9);
|
||||
border: 1px solid color.adjust($dark-color, $alpha: -0.9);
|
||||
border-radius: 4px;
|
||||
|
||||
&__details {
|
||||
flex-basis: 40%;
|
||||
@include flex($align: start, $gap: 0.5rem);
|
||||
|
||||
& img {
|
||||
width: 6rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
&__desc {
|
||||
flex-basis: 60%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,10 @@
|
||||
@use 'sass:color';
|
||||
@use '../abstracts' as *;
|
||||
|
||||
.active-link {
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
/* Navbar */
|
||||
.nav-menu {
|
||||
background-color: $dark-color;
|
||||
@ -41,7 +46,7 @@
|
||||
transition: 0ms;
|
||||
|
||||
&:hover {
|
||||
background-color: transparentize($light-color, 0.85);
|
||||
background-color: color.adjust($light-color, $alpha: -0.85);
|
||||
}
|
||||
|
||||
i.sidenav-icon {
|
||||
@ -54,7 +59,7 @@
|
||||
|
||||
.item.item-selected {
|
||||
color: $primary-light-color;
|
||||
background-color: transparentize($primary-light-color, 0.85);
|
||||
background-color: color.adjust($primary-light-color, $alpha: -0.85);
|
||||
font-weight: medium;
|
||||
}
|
||||
|
||||
@ -64,7 +69,7 @@
|
||||
padding: 0;
|
||||
top: 0;
|
||||
right: -1rem;
|
||||
background: lighten($primary-color, 15%);
|
||||
background: color.adjust($primary-color, $lightness: 15%);
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
aspect-ratio: 1;
|
||||
@ -77,8 +82,16 @@
|
||||
|
||||
&.collapsed {
|
||||
.nav-menu__logo {
|
||||
& h5 {
|
||||
display: none;
|
||||
.logo-container {
|
||||
@include flex(column, $align: center, $gap: 0.5rem);
|
||||
|
||||
&>*:not(img) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
& .nav-menu__logo-text {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,8 +102,9 @@
|
||||
justify-content: center;
|
||||
transition: 300ms;
|
||||
|
||||
& span,
|
||||
& p {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -132,7 +146,7 @@
|
||||
}
|
||||
|
||||
.sidebarquickview {
|
||||
& > h6 {
|
||||
&>h6 {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
@ -172,3 +186,43 @@
|
||||
border-left: 5px solid #3ba4d7;
|
||||
}
|
||||
}
|
||||
|
||||
/* GLOBAL SIDEBAR TO TABS */
|
||||
@media (max-width: 700px) {
|
||||
.tab-content {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 100% !important;
|
||||
flex-direction: row !important;
|
||||
overflow-x: auto !important;
|
||||
overflow-y: hidden !important;
|
||||
white-space: nowrap !important;
|
||||
border-bottom: 1px solid rgba(20, 20, 27, 0.1) !important;
|
||||
background: white !important;
|
||||
z-index: 50 !important;
|
||||
flex-shrink: 0 !important;
|
||||
height: auto !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.sidebar a {
|
||||
display: inline-block !important;
|
||||
padding: 0.8rem 1.2rem !important;
|
||||
border-bottom: 3px solid transparent !important;
|
||||
border-left: none !important;
|
||||
}
|
||||
|
||||
.sidebar .selected-sidebar-link {
|
||||
border-left: none !important;
|
||||
border-bottom: 3px solid #3ba4d7 !important;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
/* Hide quickview headings on mobile to save space if needed */
|
||||
.sidebarquickview>h4,
|
||||
.sidebarquickview>h6 {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
@use 'sass:color';
|
||||
@use '../abstracts' as *;
|
||||
|
||||
.posts {
|
||||
@ -5,29 +6,34 @@
|
||||
margin-top: 1rem;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
|
||||
&__heading {
|
||||
@include flex(column, $justify: space-between);
|
||||
}
|
||||
|
||||
&-container {
|
||||
height: 100%;
|
||||
padding: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 2rem;
|
||||
border: 1px solid transparentize($dark-color, 0.9);
|
||||
border: 1px solid color.adjust($dark-color, $alpha: -0.9);
|
||||
border-radius: 4px;
|
||||
overflow: auto;
|
||||
|
||||
&-card {
|
||||
min-height: 240px;
|
||||
flex-direction: column;
|
||||
border: 1px solid transparentize($dark-color, 0.5);
|
||||
border: 1px solid color.adjust($dark-color, $alpha: -0.5);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
|
||||
& img {
|
||||
flex-basis: 90%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
& p {
|
||||
padding: 0 0.125rem;
|
||||
flex-basis: 10%;
|
||||
@ -37,4 +43,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
@use 'sass:color';
|
||||
@use '../abstracts/colors' as *;
|
||||
|
||||
.progress-bar {
|
||||
@ -8,6 +9,7 @@
|
||||
background-color: $light-color;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
|
||||
&__status {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -16,6 +18,7 @@
|
||||
color: $dark-color;
|
||||
background-color: $primary-color;
|
||||
}
|
||||
|
||||
&__percent {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@ -23,6 +26,7 @@
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
&-chunks {
|
||||
position: relative;
|
||||
margin-top: 0.5rem;
|
||||
@ -32,21 +36,27 @@
|
||||
border-radius: 0.25rem;
|
||||
overflow: hidden;
|
||||
background-color: $light-color;
|
||||
|
||||
& .chunk {
|
||||
width: 100%;
|
||||
|
||||
&[data-chunkVal='0'] {
|
||||
background-color: transparentize($primary-light-color, 0.8);
|
||||
background-color: color.adjust($primary-light-color, $alpha: -0.8);
|
||||
}
|
||||
|
||||
&[data-chunkVal='1'] {
|
||||
background-color: $red-color;
|
||||
}
|
||||
|
||||
&[data-chunkVal='2'] {
|
||||
background-color: $primary-color;
|
||||
}
|
||||
|
||||
&[data-chunkVal='3'] {
|
||||
background-color: $golden-yellow-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__percent {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@ -55,4 +65,4 @@
|
||||
height: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
@use '../abstracts' as *;
|
||||
|
||||
.lobby {
|
||||
margin: 10px;
|
||||
border: 1px solid #aaa;
|
||||
@ -14,7 +16,7 @@
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.lobby > .topic {
|
||||
.lobby>.topic {
|
||||
font-size: 0.95em;
|
||||
margin-left: 25px;
|
||||
margin-bottom: 5px;
|
||||
@ -35,7 +37,7 @@
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.leftlobby > .topic {
|
||||
.leftlobby>.topic {
|
||||
font-size: 0.75em;
|
||||
margin-left: 15px;
|
||||
margin-bottom: 5px;
|
||||
@ -106,7 +108,7 @@
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.message > * {
|
||||
.message>* {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
@ -151,7 +153,7 @@ textarea.chatMsg {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.setup > .identity {
|
||||
.setup>.identity {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -162,3 +164,183 @@ textarea.chatMsg {
|
||||
.createDistantChat {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.no-lobbies {
|
||||
|
||||
.messages,
|
||||
.chatMessage,
|
||||
.setup {
|
||||
left: 165px;
|
||||
}
|
||||
}
|
||||
|
||||
/* CHAT ROOM (Single Chat) - Desktop Grid Layout */
|
||||
@media (min-width: 900px) {
|
||||
.node-panel.chat-room {
|
||||
display: grid !important;
|
||||
grid-template-columns: 250px 1fr 200px !important;
|
||||
/* Lobbies, Chat, Users */
|
||||
grid-template-rows: auto 1fr auto !important;
|
||||
/* Header, Messages, Input */
|
||||
grid-template-areas:
|
||||
"lobbies header rightbar"
|
||||
"lobbies messages rightbar"
|
||||
"lobbies input rightbar" !important;
|
||||
padding: 0 !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.node-panel.chat-room .lobbyName {
|
||||
grid-area: header;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
margin: 0;
|
||||
z-index: 10;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.node-panel.chat-room .lobbies {
|
||||
grid-area: lobbies;
|
||||
position: static !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
border-right: 1px solid #ccc;
|
||||
overflow-y: auto;
|
||||
display: block !important;
|
||||
top: auto !important;
|
||||
bottom: auto !important;
|
||||
left: auto !important;
|
||||
}
|
||||
|
||||
.node-panel.chat-room .messages {
|
||||
grid-area: messages;
|
||||
position: static !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
left: auto !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
bottom: auto !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.node-panel.chat-room .rightbar {
|
||||
grid-area: rightbar;
|
||||
position: static !important;
|
||||
width: auto !important;
|
||||
border-left: 1px solid #ccc;
|
||||
overflow-y: auto;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.node-panel.chat-room .chatMessage {
|
||||
grid-area: input;
|
||||
position: static !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
border-top: 1px solid #eee;
|
||||
left: auto !important;
|
||||
right: auto !important;
|
||||
bottom: auto !important;
|
||||
flex: 0 0 auto;
|
||||
padding: 10px !important;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile Overrides - Ensure Flex Column */
|
||||
@media (max-width: 899px) {
|
||||
.node-panel.chat-room {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
height: 100% !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.node-panel.chat-room .lobbyName {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.node-panel.chat-room .messages {
|
||||
flex: 1 !important;
|
||||
overflow-y: auto !important;
|
||||
position: relative !important;
|
||||
top: 0 !important;
|
||||
bottom: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.node-panel.chat-room .chatMessage {
|
||||
flex: 0 0 auto !important;
|
||||
position: relative !important;
|
||||
bottom: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.node-panel.chat-room .rightbar,
|
||||
.node-panel.chat-room .lobbies {
|
||||
display: none !important;
|
||||
position: fixed !important;
|
||||
top: 60px !important;
|
||||
bottom: 0 !important;
|
||||
width: 80% !important;
|
||||
background: white !important;
|
||||
z-index: 200 !important;
|
||||
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
.node-panel.chat-room.show-lobbies .lobbies {
|
||||
display: block !important;
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
.node-panel.chat-room.show-users .rightbar {
|
||||
display: block !important;
|
||||
right: 0 !important;
|
||||
}
|
||||
|
||||
.chat-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 150;
|
||||
}
|
||||
|
||||
.show-lobbies .chat-overlay,
|
||||
.show-users .chat-overlay {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Mobile Icons in Header */
|
||||
.mobile-menu-icons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.mobile-menu-icons i {
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 900px) {
|
||||
.mobile-menu-icons {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@ -279,3 +279,117 @@ table.friendsfiles td:nth-child(2) {
|
||||
}
|
||||
@include flex($justify: space-between);
|
||||
}
|
||||
|
||||
/* FILES MODULE RESPONSIVENESS */
|
||||
@media (max-width: 700px) {
|
||||
|
||||
/* General Files Layout */
|
||||
.file-view__body-details {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* File Detail Grid (Downloads/Uploads) */
|
||||
.file-view__body-details-stat {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.file-view__body-details-stat span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Share Manager Table -> Cards */
|
||||
.share-manager__table,
|
||||
.share-manager__table thead,
|
||||
.share-manager__table tbody,
|
||||
.share-manager__table tr,
|
||||
.share-manager__table td {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.share-manager__table thead {
|
||||
display: none;
|
||||
/* Hide header on mobile */
|
||||
}
|
||||
|
||||
.share-manager__table tr {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.5rem;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.share-manager__table td {
|
||||
margin-bottom: 0.5rem;
|
||||
border: none !important;
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
/* My Files / Friends Files Tables -> Cards */
|
||||
table.myfiles,
|
||||
table.myfiles tr,
|
||||
table.myfiles td,
|
||||
table.friendsfiles,
|
||||
table.friendsfiles tr,
|
||||
table.friendsfiles td {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table.myfiles th,
|
||||
table.friendsfiles th {
|
||||
display: none;
|
||||
}
|
||||
|
||||
table.myfiles tr,
|
||||
table.friendsfiles tr {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.5rem;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* Search Container Layout */
|
||||
.file-search-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.file-search-container__keywords {
|
||||
flex-basis: auto;
|
||||
width: 100%;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid rgba(20, 20, 27, 0.1);
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Search results table -> cards */
|
||||
.results-container,
|
||||
.results-container thead,
|
||||
.results-container tbody,
|
||||
.results-container tr,
|
||||
.results-container td {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.results-container thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.results-container tr {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.results-container td {
|
||||
margin-bottom: 0.5rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
@use 'sass:color';
|
||||
@use '../abstracts' as *;
|
||||
|
||||
.homepage {
|
||||
@ -6,20 +7,25 @@
|
||||
|
||||
.logo {
|
||||
@include flex($justify: center, $align: center);
|
||||
|
||||
& img {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.retroshareText {
|
||||
@include flex(column, $align: center);
|
||||
|
||||
& .retrotext {
|
||||
font-size: 36px;
|
||||
font-weight: 600;
|
||||
line-height: 1.125;
|
||||
& > span {
|
||||
|
||||
&>span {
|
||||
color: $primary-retro-color;
|
||||
}
|
||||
}
|
||||
& > b {
|
||||
|
||||
&>b {
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
@ -28,30 +34,36 @@
|
||||
|
||||
.certificate {
|
||||
@include flex(column, $gap: 4rem);
|
||||
|
||||
&__heading {
|
||||
text-align: center;
|
||||
& > h1 {
|
||||
|
||||
&>h1 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
@include flex(column, $gap: 2rem);
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
border: 1.5px solid transparentize($primary-retro-color, 0.8);
|
||||
border: 1.5px solid color.adjust($primary-retro-color, $alpha: -0.8);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0px 0px 8px 2px transparentize($dark-color, 0.95);
|
||||
.rsId > p {
|
||||
box-shadow: 0px 0px 8px 2px color.adjust($dark-color, $alpha: -0.95);
|
||||
|
||||
.rsId>p {
|
||||
margin-bottom: 0.5rem;
|
||||
color: $primary-retro-color;
|
||||
}
|
||||
|
||||
.retroshareID {
|
||||
padding: 0.25rem;
|
||||
@include flex($align: center);
|
||||
justify-self: start;
|
||||
font-size: 1.25rem;
|
||||
border-radius: 4px;
|
||||
background: transparentize($dark-color, 0.95);
|
||||
background: color.adjust($dark-color, $alpha: -0.95);
|
||||
|
||||
& .textArea {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
@ -62,10 +74,12 @@
|
||||
border: none;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
& i {
|
||||
color: $primary-retro-color;
|
||||
}
|
||||
& > i {
|
||||
|
||||
&>i {
|
||||
margin: 0 0.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -76,27 +90,31 @@
|
||||
background: whitesmoke;
|
||||
@include flex($justify: center, $align: center, $gap: 0.5rem);
|
||||
border-radius: 4px;
|
||||
border: 1px solid transparentize($dark-color, 0.5);
|
||||
border: 1px solid color.adjust($dark-color, $alpha: -0.5);
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
|
||||
&-container {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $light-color;
|
||||
border: 1px solid $dark-color;
|
||||
}
|
||||
& > i {
|
||||
|
||||
&>i {
|
||||
font-size: 1.2rem;
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
.add-friend > h6,
|
||||
.webhelp-container > h6 {
|
||||
|
||||
.add-friend>h6,
|
||||
.webhelp-container>h6 {
|
||||
font-weight: normal;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,16 @@
|
||||
@use 'sass:color';
|
||||
@use '../abstracts' as *;
|
||||
|
||||
.login-page {
|
||||
background-image: linear-gradient(
|
||||
-45deg,
|
||||
transparentize($primary-color, 0.25),
|
||||
transparentize($primary-retro-color, 0.25)
|
||||
);
|
||||
background-image: linear-gradient(-45deg,
|
||||
color.adjust($primary-color, $alpha: -0.25),
|
||||
color.adjust($primary-retro-color, $alpha: -0.25));
|
||||
height: 100%;
|
||||
animation: fadein 0.5s;
|
||||
|
||||
.login-container {
|
||||
background-color: white;
|
||||
box-shadow: 3px 3px 5px transparentize($dark-color, 0.6);
|
||||
box-shadow: 3px 3px 5px color.adjust($dark-color, $alpha: -0.6);
|
||||
margin: auto;
|
||||
position: relative;
|
||||
top: 100px;
|
||||
@ -27,21 +27,24 @@
|
||||
& * {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
& > img {
|
||||
|
||||
&>img {
|
||||
margin: 1rem 0 2rem;
|
||||
}
|
||||
|
||||
& extra {
|
||||
margin: 0;
|
||||
}
|
||||
& > a {
|
||||
|
||||
&>a {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.extra > label,
|
||||
.extra > br,
|
||||
.extra > input {
|
||||
.extra>label,
|
||||
.extra>br,
|
||||
.extra>input {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,132 +1,240 @@
|
||||
lockfileVersion: '6.0'
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
devDependencies:
|
||||
sass:
|
||||
specifier: ^1.60.0
|
||||
version: 1.60.0
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
sass:
|
||||
specifier: ^1.60.0
|
||||
version: 1.97.3
|
||||
|
||||
packages:
|
||||
|
||||
/anymatch@3.1.3:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
dependencies:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
'@parcel/watcher-android-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
/binary-extensions@2.2.0:
|
||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/braces@3.0.2:
|
||||
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
fill-range: 7.0.1
|
||||
dev: true
|
||||
|
||||
/chokidar@3.5.3:
|
||||
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
dependencies:
|
||||
anymatch: 3.1.3
|
||||
braces: 3.0.2
|
||||
glob-parent: 5.1.2
|
||||
is-binary-path: 2.1.0
|
||||
is-glob: 4.0.3
|
||||
normalize-path: 3.0.0
|
||||
readdirp: 3.6.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/fill-range@7.0.1:
|
||||
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
dev: true
|
||||
|
||||
/fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
'@parcel/watcher-darwin-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
dev: true
|
||||
'@parcel/watcher-darwin-x64@2.5.6':
|
||||
resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
/immutable@4.3.0:
|
||||
resolution: {integrity: sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==}
|
||||
dev: true
|
||||
'@parcel/watcher-freebsd-x64@2.5.6':
|
||||
resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
/is-binary-path@2.1.0:
|
||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||
'@parcel/watcher-linux-arm-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.6':
|
||||
resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.6':
|
||||
resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.6':
|
||||
resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher-win32-ia32@2.5.6':
|
||||
resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher-win32-x64@2.5.6':
|
||||
resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher@2.5.6':
|
||||
resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
binary-extensions: 2.2.0
|
||||
dev: true
|
||||
|
||||
/is-extglob@2.1.1:
|
||||
immutable@5.1.5:
|
||||
resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/is-glob@4.0.3:
|
||||
is-glob@4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
picomatch@4.0.3:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
readdirp@4.1.2:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
|
||||
sass@1.97.3:
|
||||
resolution: {integrity: sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-darwin-arm64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-darwin-x64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-freebsd-x64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm-glibc@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-ia32@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-x64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher@2.5.6':
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
is-glob: 4.0.3
|
||||
node-addon-api: 7.1.1
|
||||
picomatch: 4.0.3
|
||||
optionalDependencies:
|
||||
'@parcel/watcher-android-arm64': 2.5.6
|
||||
'@parcel/watcher-darwin-arm64': 2.5.6
|
||||
'@parcel/watcher-darwin-x64': 2.5.6
|
||||
'@parcel/watcher-freebsd-x64': 2.5.6
|
||||
'@parcel/watcher-linux-arm-glibc': 2.5.6
|
||||
'@parcel/watcher-linux-arm-musl': 2.5.6
|
||||
'@parcel/watcher-linux-arm64-glibc': 2.5.6
|
||||
'@parcel/watcher-linux-arm64-musl': 2.5.6
|
||||
'@parcel/watcher-linux-x64-glibc': 2.5.6
|
||||
'@parcel/watcher-linux-x64-musl': 2.5.6
|
||||
'@parcel/watcher-win32-arm64': 2.5.6
|
||||
'@parcel/watcher-win32-ia32': 2.5.6
|
||||
'@parcel/watcher-win32-x64': 2.5.6
|
||||
optional: true
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
|
||||
detect-libc@2.1.2:
|
||||
optional: true
|
||||
|
||||
immutable@5.1.5: {}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
optional: true
|
||||
|
||||
is-glob@4.0.3:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/is-number@7.0.0:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
dev: true
|
||||
node-addon-api@7.1.1:
|
||||
optional: true
|
||||
|
||||
/normalize-path@3.0.0:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
picomatch@4.0.3:
|
||||
optional: true
|
||||
|
||||
/picomatch@2.3.1:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
engines: {node: '>=8.6'}
|
||||
dev: true
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
/readdirp@3.6.0:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
sass@1.97.3:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
chokidar: 4.0.3
|
||||
immutable: 5.1.5
|
||||
source-map-js: 1.2.1
|
||||
optionalDependencies:
|
||||
'@parcel/watcher': 2.5.6
|
||||
|
||||
/sass@1.60.0:
|
||||
resolution: {integrity: sha512-updbwW6fNb5gGm8qMXzVO7V4sWf7LMXnMly/JEyfbfERbVH46Fn6q02BX7/eHTdKpE7d+oTkMMQpFWNUMfFbgQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
chokidar: 3.5.3
|
||||
immutable: 4.3.0
|
||||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
/source-map-js@1.0.2:
|
||||
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/to-regex-range@5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
dev: true
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -72,7 +72,7 @@ win32-g++ {
|
||||
} else {
|
||||
# create dummy files, we need it to include files on first try
|
||||
|
||||
system(webui-src/make-src/build.sh .)
|
||||
system(bash webui-src/make-src/build.sh .)
|
||||
|
||||
WEBUI_SRC_SCRIPT = webui-src/make-src/build.sh
|
||||
|
||||
@ -80,19 +80,19 @@ win32-g++ {
|
||||
|
||||
create_webfiles_html.output = webui/index.html
|
||||
create_webfiles_html.input = WEBUI_SRC_HTML
|
||||
create_webfiles_html.commands = sh $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ index.html .
|
||||
create_webfiles_html.commands = bash $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ index.html .
|
||||
create_webfiles_html.variable_out = JUNK
|
||||
create_webfiles_html.CONFIG = combine no_link
|
||||
|
||||
create_webfiles_js.output = webui/app.js
|
||||
create_webfiles_js.input = WEBUI_SRC_JS
|
||||
create_webfiles_js.commands = sh $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ app.js .
|
||||
create_webfiles_js.commands = bash $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ app.js .
|
||||
create_webfiles_js.variable_out = JUNK
|
||||
create_webfiles_js.CONFIG = combine no_link
|
||||
|
||||
create_webfiles_css.output = webui/styles.css
|
||||
create_webfiles_css.input = WEBUI_SRC_CSS
|
||||
create_webfiles_css.commands = sh $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ styles.css .
|
||||
create_webfiles_css.commands = bash $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_ styles.css .
|
||||
create_webfiles_css.variable_out = JUNK
|
||||
create_webfiles_css.CONFIG = combine no_link
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user