From f09d6b0ca91fb99c1acbde2db40ebb4391c7e001 Mon Sep 17 00:00:00 2001 From: zeners Date: Tue, 20 Apr 2021 15:00:39 +0200 Subject: [PATCH] try to fix eslint and prettier issues (#32) * fix eslint + prettier issues * fix eslint config (remove with prettier clashing indent rules) * fix eslint warnings --- webui-src/.eslintrc.json | 4 - webui-src/app/_reset.scss | 147 +- webui-src/app/channels/channels.js | 19 +- webui-src/app/chat/chat.css | 165 +- webui-src/app/chat/chat.js | 659 ++-- webui-src/app/config/config_files.js | 171 +- webui-src/app/config/config_network.js | 156 +- webui-src/app/config/config_node.js | 21 +- webui-src/app/config/config_people.js | 83 +- webui-src/app/config/config_resolver.js | 24 +- webui-src/app/config/config_services.js | 98 +- webui-src/app/config/config_util.js | 17 +- webui-src/app/files/files.css | 53 +- webui-src/app/files/files_downloads.js | 190 +- webui-src/app/files/files_resolver.js | 34 +- webui-src/app/files/files_search.js | 50 +- webui-src/app/files/files_uploads.js | 118 +- webui-src/app/files/files_util.js | 233 +- webui-src/app/green-black.scss | 363 ++- webui-src/app/home.js | 113 +- webui-src/app/login.js | 167 +- webui-src/app/mail/mail.css | 51 +- webui-src/app/mail/mail_draftbox.js | 23 +- webui-src/app/mail/mail_inbox.js | 23 +- webui-src/app/mail/mail_outbox.js | 23 +- webui-src/app/mail/mail_resolver.js | 76 +- webui-src/app/mail/mail_sentbox.js | 23 +- webui-src/app/mail/mail_util.js | 202 +- webui-src/app/main.js | 47 +- webui-src/app/mithril.js | 3933 ++++++++++++----------- webui-src/app/network/network.js | 102 +- webui-src/app/network/network_data.js | 51 +- webui-src/app/orange.scss | 366 ++- webui-src/app/people/people.css | 52 +- webui-src/app/people/people.js | 47 +- webui-src/app/people/people_ownids.js | 259 +- webui-src/app/people/people_util.js | 48 +- webui-src/app/rswebui.js | 212 +- webui-src/app/theme.css | 8 +- webui-src/app/widgets.js | 41 +- 40 files changed, 4502 insertions(+), 3970 deletions(-) diff --git a/webui-src/.eslintrc.json b/webui-src/.eslintrc.json index a274c1b..8287d15 100644 --- a/webui-src/.eslintrc.json +++ b/webui-src/.eslintrc.json @@ -17,10 +17,6 @@ }, "ignorePatterns": ["mithril.js", "assets/*", "make-src/*"], "rules": { - "indent": [ - "error", - 2 - ], "linebreak-style": [ "error", "unix" diff --git a/webui-src/app/_reset.scss b/webui-src/app/_reset.scss index 4747df2..4e96ea0 100644 --- a/webui-src/app/_reset.scss +++ b/webui-src/app/_reset.scss @@ -1,43 +1,124 @@ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; } body { - line-height: 1; + line-height: 1; } -ol, ul { - list-style: none; +ol, +ul { + list-style: none; } -blockquote, q { - quotes: none; +blockquote, +q { + quotes: none; } -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; } table { - border-collapse: collapse; - border-spacing: 0; + border-collapse: collapse; + border-spacing: 0; } diff --git a/webui-src/app/channels/channels.js b/webui-src/app/channels/channels.js index c362755..3a778fd 100644 --- a/webui-src/app/channels/channels.js +++ b/webui-src/app/channels/channels.js @@ -1,28 +1,20 @@ -let m = require('mithril'); -let rs = require('rswebui'); +const m = require('mithril'); - -let Channel = () => { +const Channel = () => { return { view: () => m('.channel'), }; }; -let MyChannels = () => { +const MyChannels = () => { return { - view: () => m('.widget', [ - m('h3', 'My channels'), - m('hr'), - m(Channel), - ]) + view: () => m('.widget', [m('h3', 'My channels'), m('hr'), m(Channel)]), }; }; const Layout = () => { return { - view: vnode => m('.tab-page', [ - m(MyChannels), - ]), + view: (vnode) => m('.tab-page', [m(MyChannels)]), }; }; @@ -31,4 +23,3 @@ module.exports = { return m(Layout); }, }; - diff --git a/webui-src/app/chat/chat.css b/webui-src/app/chat/chat.css index feeb173..2e53599 100644 --- a/webui-src/app/chat/chat.css +++ b/webui-src/app/chat/chat.css @@ -1,160 +1,163 @@ .lobby { - margin: 10px; - border: 1px solid #aaa; - border-radius: 20px; + margin: 10px; + border: 1px solid #aaa; + border-radius: 20px; } .lobby .mainname { - margin: 20px; - font-weight: 100; - font-size: 1.2em; + margin: 20px; + font-weight: 100; + font-size: 1.2em; } .topic { - color: #666; + color: #666; } .lobby > .topic { - font-size: 0.95em; - margin-left: 25px; - margin-bottom: 5px; + font-size: 0.95em; + margin-left: 25px; + margin-bottom: 5px; } .lefttitle { - margin-top: 15px; - margin-bottom: 0; - font-weight: 100; - font-size: 1.2em; + margin-top: 15px; + margin-bottom: 0; + font-weight: 100; + font-size: 1.2em; } .leftname { - margin-top: 5px; - margin-bottom: 5px; - padding:5px; - font-weight: 100; - font-size: 1em; + margin-top: 5px; + margin-bottom: 5px; + padding: 5px; + font-weight: 100; + font-size: 1em; } .leftlobby > .topic { - font-size: 0.75em; - margin-left: 15px; - margin-bottom: 5px; + font-size: 0.75em; + margin-left: 15px; + margin-bottom: 5px; } -.subscribed, .public { - cursor: pointer; +.subscribed, +.public { + cursor: pointer; } .leftlobby { - border: 1px solid #aaa; - border-radius: 10px; - margin-top: 5px; - background-color: white; + border: 1px solid #aaa; + border-radius: 10px; + margin-top: 5px; + background-color: white; } -.leftlobby.selected-lobby, .selectedidentity { - color: white; - background-color: #3ba4d7; +.leftlobby.selected-lobby, +.selectedidentity { + color: white; + background-color: #3ba4d7; } .rightbar { - position: absolute; - width: 185px; - background-color: white; - overflow: auto; - top: 130px; - bottom: 15px; - right: 15px; + position: absolute; + width: 185px; + background-color: white; + overflow: auto; + top: 130px; + bottom: 15px; + right: 15px; } .user { - padding: 5px; + padding: 5px; } .lobbyName { - padding: 15px; + padding: 15px; } .lobbies { - position: absolute; - width: 185px; - left:15px ; - bottom: 15px; - top: 130px; - overflow:auto; + position: absolute; + width: 185px; + left: 15px; + bottom: 15px; + top: 130px; + overflow: auto; } -.messages, .setup { - position: absolute; - background-color: white; - top: 130px; - left: 215px; - right: 215px; - overflow: auto; +.messages, +.setup { + position: absolute; + background-color: white; + top: 130px; + left: 215px; + right: 215px; + overflow: auto; } .messages { - bottom: 115px; + bottom: 115px; } .messagetext { - white-space: break-spaces; - margin-right: 5px; + white-space: break-spaces; + margin-right: 5px; } .message > * { - margin-left: 5px; + margin-left: 5px; } .username { - color: darkgreen; - font-weight: bolder; + color: darkgreen; + font-weight: bolder; } .chatMessage { - position: absolute; - background-color: white; - height: 85px; - bottom: 15px; - right: 215px; - left: 215px; + position: absolute; + background-color: white; + height: 85px; + bottom: 15px; + right: 215px; + left: 215px; } textarea.chatMsg { - height: 100%; - width: 100%; + height: 100%; + width: 100%; } .chatatchar { - margin-left: 0.2em; - margin-right: 0.2em; - color: silver; + margin-left: 0.2em; + margin-right: 0.2em; + color: silver; } .setupicon { - margin-left: 1em; - cursor: pointer; + margin-left: 1em; + cursor: pointer; } .leaveicon { - margin-left: 1em; - cursor: pointer; - color: #d40000; + margin-left: 1em; + cursor: pointer; + color: #d40000; } .selectidentity { - margin: 15px; - font-size: 1.2em; + margin: 15px; + font-size: 1.2em; } .setup > .identity { - cursor: pointer; + cursor: pointer; } .setup { - bottom: 15px; + bottom: 15px; } .createDistantChat { - margin-top: 1em; -} \ No newline at end of file + margin-top: 1em; +} diff --git a/webui-src/app/chat/chat.js b/webui-src/app/chat/chat.js index d21b148..ed35147 100644 --- a/webui-src/app/chat/chat.js +++ b/webui-src/app/chat/chat.js @@ -1,273 +1,313 @@ -let m = require('mithril'); -let rs = require('rswebui'); -let people_util = require('people/people_util'); +const m = require('mithril'); +const rs = require('rswebui'); +const peopleUtil = require('people/people_util'); // **************** utility functions ******************** function loadLobbyDetails(id, apply) { - rs.rsJsonApiRequest('/rsMsgs/getChatLobbyInfo', { + rs.rsJsonApiRequest( + '/rsMsgs/getChatLobbyInfo', + { id, }, - detail => { - if (detail.retval) { - apply(detail.info); - } + (detail) => { + if (detail.retval) { + apply(detail.info); + } }, - true, {}, + 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 + '}') - } + () => '{"id":' + id + '}' + ); +} - function sortLobbies(lobbies){ - if (lobbies !== undefined){ - let 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 - } +function sortLobbies(lobbies) { + if (lobbies !== undefined) { + 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 +} - // ***************************** models *********************************** +// ***************************** models *********************************** -let ChatRoomsModel = { +const ChatRoomsModel = { allRooms: [], - knownSubscrIds:[], // to exclude subscribed from public rooms (subscribedRooms filled to late) + knownSubscrIds: [], // to exclude subscribed from public rooms (subscribedRooms filled to late) subscribedRooms: {}, loadPublicRooms() { // TODO: this doesn't preserve id of rooms, // use regex on response to extract ids. - rs.rsJsonApiRequest('/rsMsgs/getListOfNearbyChatLobbies', {}, - data => ChatRoomsModel.allRooms = sortLobbies(data.public_lobbies), + rs.rsJsonApiRequest( + '/rsMsgs/getListOfNearbyChatLobbies', + {}, + (data) => (ChatRoomsModel.allRooms = sortLobbies(data.public_lobbies)) ); }, loadSubscribedRooms(after = null) { // ChatRoomsModel.subscribedRooms = {}; - rs.rsJsonApiRequest('/rsMsgs/getChatLobbyList', {}, + rs.rsJsonApiRequest( + '/rsMsgs/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 => { - let ids = data.cl_list.map(lid => lid.xstr64); + (data) => { + const ids = data.cl_list.map((lid) => lid.xstr64); ChatRoomsModel.knownSubscrIds = ids; - let 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; - } - })); + 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 (after != null) { - after() + after(); } - }, - ) + } + ); }, subscribed(info) { return this.knownSubscrIds.includes(info.lobby_id.xstr64); }, }; -const ChatLobbyModel = { - currentLobby: { - lobby_name: '...', - }, - lobby_user: '...', - isSubscribed: false, - messages: [], - users: [], - setupAction: (lobby_id, nick) => {}, - setIdentity(lobby_id, nick) { - rs.rsJsonApiRequest( - '/rsMsgs/setIdentityForChatLobby', - {}, - () => m.route.set('/chat/:lobby_id',{lobby_id: lobby_id}), - true, - {}, - JSON.parse, - ()=> '{"lobby_id":' + lobby_id + ',"nick":"' + nick + '"}' - ); - }, - enterPublicLobby(lobby_id, nick) { - console.info('joinVisibleChatLobby', nick, '@', lobby_id) - rs.rsJsonApiRequest( - '/rsMsgs/joinVisibleChatLobby', - {}, - () => { - loadLobbyDetails(lobby_id, info => { - ChatRoomsModel.subscribedRooms[lobby_id]= info; - ChatRoomsModel.loadSubscribedRooms(() => { - m.route.set('/chat/:lobby', { lobby: info.lobby_id.xstr64 }); - }) - }); - }, - true, - {}, - JSON.parse, - ()=> '{"lobby_id":' + lobby_id + ',"own_id":"' + nick + '"}' - ); - }, - unsubscribeChatLobby(lobby_id, follow) { - console.info('unsubscribe lobby', lobby_id) - rs.rsJsonApiRequest( - '/rsMsgs/unsubscribeChatLobby', - {}, - () => ChatRoomsModel.loadSubscribedRooms(follow), - true, - {}, - JSON.parse, - () => '{"lobby_id":' + lobby_id + '}' - ) - }, - chatId(action) { - return {type:3,lobby_id:{xstr64:m.route.param('lobby')}}; - }, - loadLobby (currentlobbyid) { - loadLobbyDetails(currentlobbyid, detail => { - this.setupAction= this.setIdentity; - this.currentLobby = detail; - this.isSubscribed = true; - this.lobby_user = rs.userList.username(detail.gxs_id) || '???'; - let 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 - 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(); - } - } - // lookup for chat-user names (only snapshot, we don't get notified about changes of participants) - var 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; - }); - }, - loadPublicLobby (currentlobbyid) { - console.info('loadPublicLobby ChatRoomsModel:',ChatRoomsModel); - this.setupAction= this.enterPublicLobby; - this.isSubscribed = false; - ChatRoomsModel.allRooms.forEach(it => { - if (it.lobby_id.xstr64 === currentlobbyid) { - this.currentLobby = it; - this.lobby_user = '???'; - this.lobbyid = currentlobbyid; - } - }) - this.users = [] - }, - sendMessage(msg, onsuccess) { - rs.rsJsonApiRequest('/rsmsgs/sendChat', {}, - () => { - // adding own message to log - rs.events[15].handler({ - mChatMessage:{ - chat_id:this.chatId(), - msg:msg, - sendTime:new Date().getTime()/1000, - lobby_peer_gxs_id:this.currentLobby.gxs_id, - } - },rs.events[15]); - onsuccess(); - }, - true, {}, undefined, - () => '{"id":{"type": 3,"lobby_id":' + m.route.param('lobby') + '}, "msg":' + JSON.stringify(msg) + '}' - ); - }, - selected(info, selName, defaultName) { - let currid = (ChatLobbyModel.currentLobby.lobby_id || {xstr64: m.route.param('lobby')}).xstr64 - return ((info.lobby_id.xstr64 === 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 - }; - }, - setupEvent(info) { - return () => { - m.route.set('/chat/:lobby/setup', { lobby: info.lobby_id.xstr64 }); - ChatLobbyModel.loadPublicLobby(info.lobby_id.xstr64); // update - }; - } -} - -// ************************* views **************************** - /** -* Message displays a single Chat-Message
-* currently removes formatting and in consequence inline links -* msg: Message to Display -*/ + * Message displays a single Chat-Message
+ * currently removes formatting and in consequence inline links + * 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); + 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.replaceAll('
','\n').replace(new RegExp('|<[^>]*>','gm'),''); + text = msg.msg + .replaceAll('
', '\n') + .replace(new RegExp('|<[^>]*>', 'gm'), ''); console.info('chat Text', text); }, - view: () => m('.message', m('span.datetime', datetime), m('span.username', username), m('span.messagetext', text)) + view: () => + m( + '.message', + m('span.datetime', datetime), + m('span.username', username), + m('span.messagetext', text) + ), }; -} +}; +const ChatLobbyModel = { + currentLobby: { + lobby_name: '...', + }, + lobby_user: '...', + isSubscribed: false, + messages: [], + users: [], + setupAction: (lobbyId, nick) => {}, + setIdentity(lobbyId, nick) { + rs.rsJsonApiRequest( + '/rsMsgs/setIdentityForChatLobby', + {}, + () => m.route.set('/chat/:lobby_id', { lobbyId }), + true, + {}, + JSON.parse, + () => '{"lobby_id":' + lobbyId + ',"nick":"' + nick + '"}' + ); + }, + enterPublicLobby(lobbyId, nick) { + console.info('joinVisibleChatLobby', nick, '@', lobbyId); + rs.rsJsonApiRequest( + '/rsMsgs/joinVisibleChatLobby', + {}, + () => { + loadLobbyDetails(lobbyId, (info) => { + ChatRoomsModel.subscribedRooms[lobbyId] = info; + ChatRoomsModel.loadSubscribedRooms(() => { + m.route.set('/chat/:lobby', { lobby: info.lobby_id.xstr64 }); + }); + }); + }, + true, + {}, + JSON.parse, + () => '{"lobby_id":' + lobbyId + ',"own_id":"' + nick + '"}' + ); + }, + unsubscribeChatLobby(lobbyId, follow) { + console.info('unsubscribe lobby', lobbyId); + rs.rsJsonApiRequest( + '/rsMsgs/unsubscribeChatLobby', + {}, + () => ChatRoomsModel.loadSubscribedRooms(follow), + true, + {}, + JSON.parse, + () => '{"lobby_id":' + lobbyId + '}' + ); + }, + chatId(action) { + return { type: 3, lobby_id: { xstr64: m.route.param('lobby') } }; + }, + loadLobby(currentlobbyid) { + loadLobbyDetails(currentlobbyid, (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 + 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(); + } + }; + // 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; + }); + }, + loadPublicLobby(currentlobbyid) { + console.info('loadPublicLobby ChatRoomsModel:', ChatRoomsModel); + this.setupAction = this.enterPublicLobby; + this.isSubscribed = false; + ChatRoomsModel.allRooms.forEach((it) => { + if (it.lobby_id.xstr64 === currentlobbyid) { + this.currentLobby = it; + this.lobby_user = '???'; + this.lobbyid = currentlobbyid; + } + }); + this.users = []; + }, + sendMessage(msg, onsuccess) { + rs.rsJsonApiRequest( + '/rsmsgs/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(); + }, + true, + {}, + undefined, + () => + '{"id":{"type": 3,"lobby_id":' + + m.route.param('lobby') + + '}, "msg":' + + JSON.stringify(msg) + + '}' + ); + }, + selected(info, selName, defaultName) { + const currid = (ChatLobbyModel.currentLobby.lobby_id || { xstr64: m.route.param('lobby') }) + .xstr64; + return (info.lobby_id.xstr64 === 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 + }; + }, + setupEvent(info) { + return () => { + m.route.set('/chat/:lobby/setup', { lobby: info.lobby_id.xstr64 }); + ChatLobbyModel.loadPublicLobby(info.lobby_id.xstr64); // update + }; + }, +}; + +// ************************* views **************************** const Lobby = () => { let info = {}; let tagname = ''; - let onclick = e => {}; + 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'; + info = v.attrs.info; + tagname = v.attrs.tagname; + onclick = v.attrs.onclick || ((e) => {}); + lobbytagname = v.attrs.lobbytagname || 'mainname'; }, - view: v => { - return m(ChatLobbyModel.selected(info, '.selected-lobby', tagname), { - key: info.lobby_id.xstr64, - onclick: onclick, - }, [ - m('h5', {class: lobbytagname},info.lobby_name === '' ? '' : info.lobby_name), - m('.topic', info.lobby_topic), - ]); + view: (v) => { + return m( + ChatLobbyModel.selected(info, '.selected-lobby', tagname), + { + key: info.lobby_id.xstr64, + onclick, + }, + [ + m('h5', { class: lobbytagname }, info.lobby_name === '' ? '' : info.lobby_name), + m('.topic', info.lobby_topic), + ] + ); }, }; }; const LobbyList = { view(vnode) { - let tagname = vnode.attrs.tagname - let lobbytagname = vnode.attrs.lobbytagname - let onclick = vnode.attrs.onclick||(() => null) + const tagname = vnode.attrs.tagname; + const lobbytagname = vnode.attrs.lobbytagname; + const onclick = vnode.attrs.onclick || (() => null); return [ m('hr'), - vnode.attrs.rooms.map(info => m(Lobby, { - info, - tagname, - lobbytagname, - onclick: onclick(info), - })) + vnode.attrs.rooms.map((info) => + m(Lobby, { + info, + tagname, + lobbytagname, + onclick: onclick(info), + }) + ), ]; }, -} +}; const SubscribedLeftLobbies = { view() { @@ -276,11 +316,11 @@ const SubscribedLeftLobbies = { m(LobbyList, { rooms: sortLobbies(Object.values(ChatRoomsModel.subscribedRooms)), tagname: '.leftlobby.subscribed', - lobbytagname:'leftname', + lobbytagname: 'leftname', onclick: ChatLobbyModel.switchToEvent, - }) - ] - } + }), + ]; + }, }; const SubscribedLobbies = { @@ -289,62 +329,74 @@ const SubscribedLobbies = { m('h3', 'Subscribed chat rooms'), m(LobbyList, { rooms: sortLobbies(Object.values(ChatRoomsModel.subscribedRooms)), - tagname:'.lobby.subscribed', + tagname: '.lobby.subscribed', onclick: ChatLobbyModel.switchToEvent, }), - ]) - } -} + ]); + }, +}; const PublicLeftLobbies = { view() { return [ m('h5.lefttitle', 'public:'), m(LobbyList, { - rooms: Object.values(ChatRoomsModel.allRooms).filter(info => !ChatRoomsModel.subscribed(info)), + rooms: Object.values(ChatRoomsModel.allRooms).filter( + (info) => !ChatRoomsModel.subscribed(info) + ), tagname: '.leftlobby.public', lobbytagname: 'leftname', onclick: ChatLobbyModel.setupEvent, }), - ] - } -} + ]; + }, +}; const PublicLobbies = () => { return m('.widget', [ m('h3', 'Public chat rooms'), m(LobbyList, { - rooms: ChatRoomsModel.allRooms.filter(info => !ChatRoomsModel.subscribed(info)), + rooms: ChatRoomsModel.allRooms.filter((info) => !ChatRoomsModel.subscribed(info)), tagname: '.lobby.public', onclick: ChatLobbyModel.setupEvent, }), - ]) + ]); }; const LobbyName = () => { - return m('h3.lobbyName', - ChatLobbyModel.isSubscribed ? [ - m('span.chatusername',ChatLobbyModel.lobby_user), - m('span.chatatchar','@'), - ] : [], - m('span.chatlobbyname',ChatLobbyModel.currentLobby.lobby_name), - m.route.param('subaction') !== 'setup' ? [ - 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 }); - }) - }) - ] : [] + return m( + 'h3.lobbyName', + ChatLobbyModel.isSubscribed + ? [m('span.chatusername', ChatLobbyModel.lobby_user), m('span.chatatchar', '@')] + : [], + m('span.chatlobbyname', ChatLobbyModel.currentLobby.lobby_name), + m.route.param('subaction') !== 'setup' + ? [ + 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 }); + }), + }), + ] + : [] ); }; @@ -352,61 +404,60 @@ const LobbyName = () => { const Layout = () => { return { - view: () => m('.tab-page', [ - m(SubscribedLobbies), - PublicLobbies(), - ]) - } + view: () => m('.tab-page', [m(SubscribedLobbies), PublicLobbies()]), + }; }; const LayoutSingle = () => { return { oninit: () => ChatLobbyModel.loadLobby(m.route.param('lobby')), - view: vnode => m('.tab-page', [ - 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') { - var msg = e.target.value; + view: (vnode) => + m('.tab-page', [ + 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=''); + ChatLobbyModel.sendMessage(msg, () => (e.target.value = '')); return false; - } - } - }) - ), - ]), + } + }, + }) + ), + ]), }; }; const LayoutSetup = () => { let ownIds = []; return { - oninit: () => people_util.ownIds(data => ownIds = data), - view: vnode => m('.tab-page', [ - 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), - )), + oninit: () => peopleUtil.ownIds((data) => (ownIds = data)), + view: (vnode) => + m('.tab-page', [ + 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) + ) + ), + ]), ]), - ]), }; }; @@ -421,32 +472,42 @@ const LayoutSetup = () => { const LayoutCreateDistant = () => { let ownIds = []; return { - oninit: () => people_util.ownIds(data => ownIds = data), - view: vnode => m('.tab-page', [ - m('.createDistantChat', [ - 'choose identitiy to chat with ', - rs.userList.username(m.route.param('lobby')), - ownIds.map(id => m('.identity', { - onclick: () => rs.rsJsonApiRequest( - '/rsMsgs/initiateDistantChatConnexion',{ - to_pid:m.route.param('lobby'), - from_pid:id, - notify: true - }, result => { - console.info('initiateDistantChatConnexion', result); - m.route.set('/chat/:lobbyid',{lobbyid:result.pid}); - } - ) - }, rs.userList.username(id))), + oninit: () => peopleUtil.ownIds((data) => (ownIds = data)), + view: (vnode) => + m('.tab-page', [ + m('.createDistantChat', [ + 'choose identitiy to chat with ', + rs.userList.username(m.route.param('lobby')), + ownIds.map((id) => + m( + '.identity', + { + onclick: () => + rs.rsJsonApiRequest( + '/rsMsgs/initiateDistantChatConnexion', + { + to_pid: m.route.param('lobby'), + from_pid: id, + notify: true, + }, + (result) => { + console.info('initiateDistantChatConnexion', result); + m.route.set('/chat/:lobbyid', { lobbyid: result.pid }); + } + ), + }, + rs.userList.username(id) + ) + ), + ]), ]), - ]), }; }; module.exports = { oninit: () => { - ChatRoomsModel.loadSubscribedRooms(); - ChatRoomsModel.loadPublicRooms(); + ChatRoomsModel.loadSubscribedRooms(); + ChatRoomsModel.loadPublicRooms(); }, view: (vnode) => { if (m.route.param('lobby') === undefined) { @@ -458,5 +519,5 @@ module.exports = { } else { return m(LayoutSingle); } - } + }, }; diff --git a/webui-src/app/config/config_files.js b/webui-src/app/config/config_files.js index 23045d7..4511b55 100644 --- a/webui-src/app/config/config_files.js +++ b/webui-src/app/config/config_files.js @@ -6,105 +6,131 @@ const SharedDirectories = () => { return { dirs: [], oninit: (vnode) => { - rs.rsJsonApiRequest('/rsFiles/getSharedDirectories', {}, - (data) => directories = data.dirs); + rs.rsJsonApiRequest('/rsFiles/getSharedDirectories', {}, (data) => (directories = data.dirs)); }, - view: (vnode) => m('.widget', [ - m('h3', 'Shared Directories'), - m('hr'), - directories.map(dir => m('input[type=text].stretched', { - value: dir.filename - })) - ]), - } + view: (vnode) => + m('.widget', [ + m('h3', 'Shared Directories'), + m('hr'), + directories.map((dir) => + m('input[type=text].stretched', { + value: dir.filename, + }) + ), + ]), + }; }; const DownloadDirectory = () => { let dlDir = ''; const setDir = () => { - const path = document.getElementById('dl-dir-input').value; - rsJsonApiRequest('rsFiles/setDownloadDirectory', { - path: dlDir, - }, () => {}); - } + // const path = document.getElementById('dl-dir-input').value; // unused? + rs.rsJsonApiRequest( + 'rsFiles/setDownloadDirectory', + { + path: dlDir, + }, + () => {} + ); + }; return { oninit: (vnode) => { - rs.rsJsonApiRequest('/rsFiles/getDownloadDirectory', {}, - (data) => dlDir = data.retval); + rs.rsJsonApiRequest('/rsFiles/getDownloadDirectory', {}, (data) => (dlDir = data.retval)); }, - view: (vnode) => m('.widget', [ - m('h3', 'Downloads Directory'), - m('hr'), - m('input[type=text].stretched#dl-dir-input', { - oninput: (e) => dlDir = e.target.value, - value: dlDir, - onchange: setDir, - }), - ]), - } + view: (vnode) => + m('.widget', [ + m('h3', 'Downloads Directory'), + m('hr'), + m('input[type=text].stretched#dl-dir-input', { + oninput: (e) => (dlDir = e.target.value), + value: dlDir, + onchange: setDir, + }), + ]), + }; }; const PartialsDirectory = () => { let partialsDir = ''; const setDir = () => { - const path = document.getElementById('partial-dir-input').value; - rsJsonApiRequest('rsFiles/setPartialsDirectory', { - path: partialDir - }, () => {}); - } + // const path = document.getElementById('partial-dir-input').value; // unused? + rs.rsJsonApiRequest( + 'rsFiles/setPartialsDirectory', + { + path: partialsDir, + }, + () => {} + ); + }; return { - oninit: (vnode) => rs.rsJsonApiRequest('/rsFiles/getPartialsDirectory', {}, - (data) => partialsDir = data.retval, - ), - view: (vnode) => m('.widget.widget-halfwidth', [ - m('h3', 'Partials Directory'), - m('hr'), - m('input[type=text].stretched#partial-dir-input', { - oninput: (e) => partialsDir = e.target.value, - value: partialsDir, - onchange: setDir, - }), - ]), + oninit: (vnode) => + rs.rsJsonApiRequest( + '/rsFiles/getPartialsDirectory', + {}, + (data) => (partialsDir = data.retval) + ), + view: (vnode) => + m('.widget.widget-halfwidth', [ + m('h3', 'Partials Directory'), + m('hr'), + m('input[type=text].stretched#partial-dir-input', { + oninput: (e) => (partialsDir = e.target.value), + value: partialsDir, + onchange: setDir, + }), + ]), }; }; const TransferOptions = () => { let strategy = undefined; let diskLimit = undefined; - const setChunkStrat = () => rs.rsJsonApiRequest('/rsFiles/setDefaultChunkStrategy', { - strategy: Number(strategy) + const setChunkStrat = () => + rs.rsJsonApiRequest( + '/rsFiles/setDefaultChunkStrategy', + { + strategy: Number(strategy), }, - () => {},); - const setFreeLimit = () => rs.rsJsonApiRequest('/rsFiles/setFreeDiskSpaceLimit', { + () => {} + ); + const setFreeLimit = () => + rs.rsJsonApiRequest( + '/rsFiles/setFreeDiskSpaceLimit', + { MinimumFreeMB: diskLimit, }, - () => {},); + () => {} + ); return { oninit: (vnode) => { - rs.rsJsonApiRequest('/rsFiles/defaultChunkStrategy', {}, - (data) => strategy = data.retval) - rs.rsJsonApiRequest('/rsFiles/freeDiskSpaceLimit', {}, - (data) => diskLimit = data.retval) + rs.rsJsonApiRequest('/rsFiles/defaultChunkStrategy', {}, (data) => (strategy = data.retval)); + rs.rsJsonApiRequest('/rsFiles/freeDiskSpaceLimit', {}, (data) => (diskLimit = data.retval)); }, - view: (vnode) => m('.widget.widget-half', [ - m('h3', 'Transfer options'), - m('hr'), - m('.grid-2col', [ - m('p', 'Default chunk strategy:'), - m('select[name=strategy]', { - oninput: (e) => strategy = e.target.value, - value: strategy, - onchange: setChunkStrat, - }, ['Streaming', 'Random', 'Progressive'].map( - (val, i) => m('option[value=' + i + ']', val))), - m('p', 'Safety disk space limit(MB):'), - m('input[type=number].small', { - oninput: (e) => diskLimit = e.target.value, - value: diskLimit, - onchange: setFreeLimit, - }), + view: (vnode) => + m('.widget.widget-half', [ + m('h3', 'Transfer options'), + m('hr'), + m('.grid-2col', [ + m('p', 'Default chunk strategy:'), + m( + 'select[name=strategy]', + { + oninput: (e) => (strategy = e.target.value), + value: strategy, + onchange: setChunkStrat, + }, + ['Streaming', 'Random', 'Progressive'].map((val, i) => + m('option[value=' + i + ']', val) + ) + ), + m('p', 'Safety disk space limit(MB):'), + m('input[type=number].small', { + oninput: (e) => (diskLimit = e.target.value), + value: diskLimit, + onchange: setFreeLimit, + }), + ]), ]), - ]), }; }; @@ -115,9 +141,8 @@ const Layout = () => { m(DownloadDirectory), m(PartialsDirectory), m(TransferOptions), - ] + ], }; }; module.exports = Layout; - diff --git a/webui-src/app/config/config_network.js b/webui-src/app/config/config_network.js index 7afd83a..eb99bc1 100644 --- a/webui-src/app/config/config_network.js +++ b/webui-src/app/config/config_network.js @@ -1,113 +1,135 @@ -let m = require('mithril'); -let rs = require('rswebui'); +const m = require('mithril'); +const rs = require('rswebui'); -let util = require('config/config_util'); +const util = require('config/config_util'); // for future use - -let SetNwMode = () => { - mode = undefined; - sslId = undefined; - setMode = () => rs.rsJsonApiRequest('/'); +/* eslint-disable no-unused-vars */ const SetNwMode = () => { + let mode = undefined; + let sslId = undefined; + const setMode = () => rs.rsJsonApiRequest('/'); return { - oninit: util.getSslId().then(val => { + oninit: util.getSslId().then((val) => { sslId = val; rs.rsJsonApiRequest('/'); }), view: () => [ m('p', 'Network mode:'), - m('select', { - oninput: (e) => mode = e.target.value, - value: mode, - onchange: setMode, - }, ['Discovery on(recommended)', 'Discovery off'].map( - (val, i) => m('option[value=' + (i + 1) + ']', - val))), + m( + 'select', + { + oninput: (e) => (mode = e.target.value), + value: mode, + onchange: setMode, + }, + ['Discovery on(recommended)', 'Discovery off'].map((val, i) => + m('option[value=' + (i + 1) + ']', val) + ) + ), ], }; }; -let SetLimits = () => { +const SetLimits = () => { let dlim = undefined; let ulim = undefined; - let setMaxRates = () => rs.rsJsonApiRequest('/rsConfig/SetMaxDataRates', { - downKb: dlim, - upKb: ulim - }); + const setMaxRates = () => + rs.rsJsonApiRequest('/rsConfig/SetMaxDataRates', { + downKb: dlim, + upKb: ulim, + }); return { - oninit: () => rs.rsJsonApiRequest('/rsConfig/GetMaxDataRates', {}, (data) => { - dlim = data.inKb; - ulim = data.outKb; - }), + oninit: () => + rs.rsJsonApiRequest('/rsConfig/GetMaxDataRates', {}, (data) => { + dlim = data.inKb; + ulim = data.outKb; + }), view: () => [ - m('p', + m( + 'p', util.tooltip( 'The download limit covers the whole application. ' + - 'However, in some situations, such as when transfering ' + - 'many files at once, the estimated bandwidth becomes ' + - 'unreliable and the total value reported by Retroshare ' + - 'might exceed that limit.' - ), 'Download limit(KB/s):'), + 'However, in some situations, such as when transfering ' + + 'many files at once, the estimated bandwidth becomes ' + + 'unreliable and the total value reported by Retroshare ' + + 'might exceed that limit.' + ), + 'Download limit(KB/s):' + ), + m('input[type=number][name=download]', { + value: dlim, + oninput: (e) => (dlim = Number(e.target.value)), + onchange: setMaxRates, + }), m( - 'input[type=number][name=download]', { - value: dlim, - oninput: (e) => dlim = Number(e.target.value), - onchange: setMaxRates, - }), - m('p', + 'p', util.tooltip( 'The upload limit covers the entire software. ' + - 'Too small an upload limit may eventually block ' + - 'low priority services(forums, channels). ' + - 'A minimum recommended value is 50KB/s.' - ), 'Upload limit(KB/s):'), + 'Too small an upload limit may eventually block ' + + 'low priority services(forums, channels). ' + + 'A minimum recommended value is 50KB/s.' + ), + 'Upload limit(KB/s):' + ), m('input[type=number][name=upload]', { value: ulim, - oninput: (e) => ulim = Number(e.target.value), + oninput: (e) => (ulim = Number(e.target.value)), onchange: setMaxRates, }), ], }; }; -SetOpMode = () => { - opmode = undefined; - setmode = () => rs.rsJsonApiRequest('/rsconfig/SetOperatingMode', { - opMode: Number(opmode) - }, () => {}); +const SetOpMode = () => { + let opmode = undefined; + const setmode = () => + rs.rsJsonApiRequest( + '/rsconfig/SetOperatingMode', + { + opMode: Number(opmode), + }, + () => {} + ); return { - oninit: () => rs.rsJsonApiRequest('/rsConfig/getOperatingMode', {}, - (data) => opmode = data.retval), + oninit: () => + rs.rsJsonApiRequest('/rsConfig/getOperatingMode', {}, (data) => (opmode = data.retval)), view: () => [ - m('p', 'Operating mode:', + m( + 'p', + 'Operating mode:', util.tooltip( `No Anon D/L: Switches off file forwarding\n Gaming Mode: 25% standard traffic and TODO: Reduced popups\n Low traffic: 10% standard traffic and TODO: pause all file transfers\n` - ), + ) + ), + m( + 'select', + { + oninput: (e) => (opmode = e.target.value), + value: opmode, + onchange: setmode, + }, + ['Normal', 'No Anon D/L', 'Gaming', 'Low traffic'].map((val, i) => + m('option[value=' + (i + 1) + ']', val) + ) ), - m('select', { - oninput: (e) => opmode = e.target.value, - value: opmode, - onchange: setmode, - }, ['Normal', 'No Anon D/L', 'Gaming', 'Low traffic'].map( - (val, i) => m('option[value=' + (i + 1) + ']', val))), ], }; }; -let Component = () => { +const Component = () => { return { - view: () => m('.widget.widget-half', [ - m('h3', 'Network Configuration'), - m('hr'), - m('.grid-2col', [ - m(SetLimits), - m(SetOpMode), - //m(SetNwMode), + view: () => + m('.widget.widget-half', [ + m('h3', 'Network Configuration'), + m('hr'), + m('.grid-2col', [ + m(SetLimits), + m(SetOpMode), + // m(SetNwMode), + ]), ]), - ]), }; }; module.exports = Component; - diff --git a/webui-src/app/config/config_node.js b/webui-src/app/config/config_node.js index b5bf5d0..daaf4b7 100644 --- a/webui-src/app/config/config_node.js +++ b/webui-src/app/config/config_node.js @@ -1,18 +1,17 @@ -let m = require('mithril'); -let rs = require('rswebui'); +const m = require('mithril'); +const rs = require('rswebui'); const Node = () => { - nodeInfo = { - setData: function(data) { + const nodeInfo = { + setData(data) { Object.assign(nodeInfo, data.status); }, }; return { - oninit: function() { - rs.rsJsonApiRequest('/rsConfig/getConfigNetStatus', {}, nodeInfo - .setData); + oninit() { + rs.rsJsonApiRequest('/rsConfig/getConfigNetStatus', {}, nodeInfo.setData); }, - view: function() { + view() { return [ m('.widget.widget-half', [ m('h3', 'Public Information'), @@ -24,14 +23,12 @@ const Node = () => { m('li', 'Port Forwarding: ' + nodeInfo.forwardPort), m('li', 'DHT: ' + nodeInfo.DHTActive), m('li', 'uPnP: ' + nodeInfo.uPnPActive), - m('li', 'Local Address: ' + nodeInfo.localAddr + - ' Port: ' + nodeInfo.localPort), + m('li', 'Local Address: ' + nodeInfo.localAddr + ' Port: ' + nodeInfo.localPort), ]), ]), ]; }, - } + }; }; module.exports = Node; - diff --git a/webui-src/app/config/config_people.js b/webui-src/app/config/config_people.js index b06cfff..23b903b 100644 --- a/webui-src/app/config/config_people.js +++ b/webui-src/app/config/config_people.js @@ -6,51 +6,58 @@ const Reputation = () => { let deleteBannedAfter = undefined; return { oninit: (vnode) => { - rs.rsJsonApiRequest('/rsIdentity/autoAddFriendIdsAsContact', {}, - (data) => addFriendIdAsContacts = data.retval); - rs.rsJsonApiRequest('/rsIdentity/deleteBannedNodesThreshold', {}, - (data) => deleteBannedAfter = data.retval); + rs.rsJsonApiRequest( + '/rsIdentity/autoAddFriendIdsAsContact', + {}, + (data) => (addFriendIdAsContacts = data.retval) + ); + rs.rsJsonApiRequest( + '/rsIdentity/deleteBannedNodesThreshold', + {}, + (data) => (deleteBannedAfter = data.retval) + ); }, - view: (vnode) => m('.widget', [ - m('h3', 'Reputation'), - m('hr'), - m('.grid-2col', [ - m('p', - 'Automatically add identities owned by friend nodes to my contacts:' - ), - m('input[type=checkbox]', { - checked: addFriendIdAsContacts, - oninput: (e) => { - addFriendIdAsContacts = e.target.checked; - rs.rsJsonApiRequest( - '/rsIdentity/setAutoAddFriendIdsAsContact', { - enabled: addFriendIdAsContacts - }, () => {}); - }, - }), - m('p', - 'Delete banned identities after(in days, 0 means indefinitely):' - ), - m('input[type=number]', { - oninput: (e) => deleteBannedAfter = e.target.value, - value: deleteBannedAfter, - onchange: () => rs.rsJsonApiRequest( - '/rsIdentity/setDeleteBannedNodesThreshold', { - days: deleteBannedAfter - }, () => {}), - }), + view: (vnode) => + m('.widget', [ + m('h3', 'Reputation'), + m('hr'), + m('.grid-2col', [ + m('p', 'Automatically add identities owned by friend nodes to my contacts:'), + m('input[type=checkbox]', { + checked: addFriendIdAsContacts, + oninput: (e) => { + addFriendIdAsContacts = e.target.checked; + rs.rsJsonApiRequest( + '/rsIdentity/setAutoAddFriendIdsAsContact', + { + enabled: addFriendIdAsContacts, + }, + () => {} + ); + }, + }), + m('p', 'Delete banned identities after(in days, 0 means indefinitely):'), + m('input[type=number]', { + oninput: (e) => (deleteBannedAfter = e.target.value), + value: deleteBannedAfter, + onchange: () => + rs.rsJsonApiRequest( + '/rsIdentity/setDeleteBannedNodesThreshold', + { + days: deleteBannedAfter, + }, + () => {} + ), + }), + ]), ]), - ]), - } + }; }; const Layout = () => { return { - view: (vnode) => [ - m(Reputation), - ] + view: (vnode) => [m(Reputation)], }; }; module.exports = Layout; - diff --git a/webui-src/app/config/config_resolver.js b/webui-src/app/config/config_resolver.js index 2997ffe..3544e5e 100644 --- a/webui-src/app/config/config_resolver.js +++ b/webui-src/app/config/config_resolver.js @@ -1,9 +1,7 @@ -let m = require('mithril'); -let rs = require('rswebui'); -let widget = require('widgets'); +const m = require('mithril'); +const widget = require('widgets'); - -let sections = { +const sections = { network: require('config/config_network'), node: require('config/config_node'), services: require('config/config_services'), @@ -12,13 +10,14 @@ let sections = { }; const Layout = { - view: vnode => m('.tab-page', [ - m(widget.Sidebar, { - tabs: Object.keys(sections), - baseRoute: '/config/', - }), - m('.node-panel', vnode.children), - ]) + view: (vnode) => + m('.tab-page', [ + m(widget.Sidebar, { + tabs: Object.keys(sections), + baseRoute: '/config/', + }), + m('.node-panel', vnode.children), + ]), }; module.exports = { @@ -27,4 +26,3 @@ module.exports = { return m(Layout, m(sections[tab])); }, }; - diff --git a/webui-src/app/config/config_services.js b/webui-src/app/config/config_services.js index db62332..5cfd7df 100644 --- a/webui-src/app/config/config_services.js +++ b/webui-src/app/config/config_services.js @@ -1,64 +1,69 @@ -let m = require('mithril'); -let rs = require('rswebui'); +const m = require('mithril'); +const rs = require('rswebui'); -let servicesInfo = { +const servicesInfo = { list: [], - setData: function(data) { + setData(data) { servicesInfo.list = data.info.mServiceList; }, }; -let Service = () => { +const Service = () => { let defaultAllowed = undefined; return { - oninit: (v) => rs.rsJsonApiRequest( - '/rsServiceControl/getServicePermissions', { - serviceId: v.attrs.data.key - }, (retval) => defaultAllowed = retval.permissions.mDefaultAllowed), - view: (v) => m('tr', { - key: v.attrs.data.key, - }, [ - m('td', v.attrs.data.value.mServiceName), - m('td', v.attrs.data.value.mServiceType), - m('td', v.attrs.data.value.mVersionMajor + - '.' + - v.attrs.data.value.mVersionMinor), - m('td', m('input[type=checkbox]', { - checked: defaultAllowed, - oninput: (e) => { - defaultAllowed = e.target.checked; - rs.rsJsonApiRequest( - '/rsServiceControl/updateServicePermissions', { - serviceId: v.attrs.data.key, - permissions: { - mDefaultAllowed: defaultAllowed - } - }); + oninit: (v) => + rs.rsJsonApiRequest( + '/rsServiceControl/getServicePermissions', + { + serviceId: v.attrs.data.key, }, - })), - ]), + (retval) => (defaultAllowed = retval.permissions.mDefaultAllowed) + ), + view: (v) => + m( + 'tr', + { + key: v.attrs.data.key, + }, + [ + m('td', v.attrs.data.value.mServiceName), + m('td', v.attrs.data.value.mServiceType), + m('td', v.attrs.data.value.mVersionMajor + '.' + v.attrs.data.value.mVersionMinor), + m( + 'td', + m('input[type=checkbox]', { + checked: defaultAllowed, + oninput: (e) => { + defaultAllowed = e.target.checked; + rs.rsJsonApiRequest('/rsServiceControl/updateServicePermissions', { + serviceId: v.attrs.data.key, + permissions: { + mDefaultAllowed: defaultAllowed, + }, + }); + }, + }) + ), + ] + ), }; }; -let MyServices = { - oninit: function() { - rs.rsJsonApiRequest('/rsServiceControl/getOwnServices', {}, - servicesInfo.setData); +const MyServices = { + oninit() { + rs.rsJsonApiRequest('/rsServiceControl/getOwnServices', {}, servicesInfo.setData); }, - view: function() { + view() { return m('.widget', [ m('h3', 'My Services'), m('hr'), m('table', [ - m('tr', [ - m('th', 'Name'), - m('th', 'ID'), - m('th', 'Version'), - m('th', 'Allow by default'), - ]), - servicesInfo.list.map((data) => m(Service, { - data, - })), + m('tr', [m('th', 'Name'), m('th', 'ID'), m('th', 'Version'), m('th', 'Allow by default')]), + servicesInfo.list.map((data) => + m(Service, { + data, + }) + ), ]), ]); }, @@ -66,9 +71,7 @@ let MyServices = { const Layout = () => { return { - view: vnode => [ - m(MyServices), - ], + view: (vnode) => [m(MyServices)], }; }; @@ -77,4 +80,3 @@ module.exports = { return m(Layout); }, }; - diff --git a/webui-src/app/config/config_util.js b/webui-src/app/config/config_util.js index 9dcfb75..b8d8be7 100644 --- a/webui-src/app/config/config_util.js +++ b/webui-src/app/config/config_util.js @@ -1,22 +1,17 @@ -let m = require('mithril'); -let rs = require('rswebui'); +const m = require('mithril'); +const rs = require('rswebui'); function tooltip(text) { - return m('.tooltip', [ - m('i.fas.fa-info-circle'), - m('.tooltiptext', text) - ]); -}; + return m('.tooltip', [m('i.fas.fa-info-circle'), m('.tooltiptext', text)]); +} async function getSslId() { let id = ''; - await rs.rsJsonApiRequest('/rsIdentity/GetOwnSignedIds', {}, - (data) => id = data.ids[0]); + await rs.rsJsonApiRequest('/rsIdentity/GetOwnSignedIds', {}, (data) => (id = data.ids[0])); return id; -}; +} module.exports = { tooltip, getSslId, }; - diff --git a/webui-src/app/files/files.css b/webui-src/app/files/files.css index 1313ab7..d97f314 100644 --- a/webui-src/app/files/files.css +++ b/webui-src/app/files/files.css @@ -1,35 +1,38 @@ .file-view { - width: 90%; - border-radius: 10px; - border: 1px solid #ccc; - margin-top: 2em; - padding: 1em; - animation: fadein 0.5s; -}.file-view > p { - margin: 0.5em; - font-size: 1.1em; -}.file-view > span { - margin: 0 1em; - font-size: 0.9em; -}.file-view > .progressbar { - width: 70%; - margin-bottom: 5px; -}.file-view button { - float: right; - margin-left: 15px; + width: 90%; + border-radius: 10px; + border: 1px solid #ccc; + margin-top: 2em; + padding: 1em; + animation: fadein 0.5s; +} +.file-view > p { + margin: 0.5em; + font-size: 1.1em; +} +.file-view > span { + margin: 0 1em; + font-size: 0.9em; +} +.file-view > .progressbar { + width: 70%; + margin-bottom: 5px; +} +.file-view button { + float: right; + margin-left: 15px; } span > i { - margin-right: 5px; + margin-right: 5px; } .filestat { - min-width: 7em; - display: inline-block; + min-width: 7em; + display: inline-block; } .searchbar { - margin: 5px; - width: 40%; - box-shadow: 0 0 3px #aaa; + margin: 5px; + width: 40%; + box-shadow: 0 0 3px #aaa; } - diff --git a/webui-src/app/files/files_downloads.js b/webui-src/app/files/files_downloads.js index 25a712b..3c941a7 100644 --- a/webui-src/app/files/files_downloads.js +++ b/webui-src/app/files/files_downloads.js @@ -1,67 +1,61 @@ -let m = require('mithril'); -let rs = require('rswebui'); -let util = require('files/files_util') -let widget = require('widgets'); +const m = require('mithril'); +const rs = require('rswebui'); +const util = require('files/files_util'); +const widget = require('widgets'); - -function updateFileDetail(hash, isNew = false) { - rs.rsJsonApiRequest( - '/rsFiles/FileDetails', { - hash, - hintflags: 16, // RS_FILE_HINTS_DOWNLOAD - }, - (fileStat) => { - if(!fileStat.retval) { - console.error('Error: Unknown hash in Downloads: ', hash); - return; - } - fileStat.info.isSearched = (isNew ? - true : - Downloads.statusMap[hash].isSearched); - Downloads.statusMap[hash] = fileStat.info; - }, - ); -} - -let Downloads = { +const Downloads = { statusMap: {}, hashes: [], loadHashes() { - rs.rsJsonApiRequest( - '/rsFiles/FileDownloads', {}, - (d) => Downloads.hashes = d.hashs, - ); + rs.rsJsonApiRequest('/rsFiles/FileDownloads', {}, (d) => (Downloads.hashes = d.hashs)); }, loadStatus() { Downloads.loadHashes(); - let fileKeys = Object.keys(Downloads.statusMap); - if(Downloads.hashes.length !== fileKeys.length) { + const fileKeys = Object.keys(Downloads.statusMap); + if (Downloads.hashes.length !== fileKeys.length) { // New file added - if(Downloads.hashes.length > fileKeys.length) { - let newHashes = util.compareArrays(Downloads.hashes, fileKeys); - for(let hash of newHashes) { - updateFileDetail(hash, true); + if (Downloads.hashes.length > fileKeys.length) { + const newHashes = util.compareArrays(Downloads.hashes, fileKeys); + for (const hash of newHashes) { + Downloads.updateFileDetail(hash, true); } } // Existing file removed else { - let oldHashes = util.compareArrays(fileKeys, Downloads.hashes); - for(let hash of oldHashes) { + const oldHashes = util.compareArrays(fileKeys, Downloads.hashes); + for (const hash of oldHashes) { delete Downloads.statusMap[hash]; } } } - for(let hash in Downloads.statusMap) { - updateFileDetail(hash); + for (const hash in Downloads.statusMap) { + Downloads.updateFileDetail(hash); } }, resetSearch() { - for(let hash in Downloads.statusMap) { + for (const hash in Downloads.statusMap) { Downloads.statusMap[hash].isSearched = true; } }, + updateFileDetail(hash, isNew = false) { + rs.rsJsonApiRequest( + '/rsFiles/FileDetails', + { + hash, + hintflags: 16, // RS_FILE_HINTS_DOWNLOAD + }, + (fileStat) => { + if (!fileStat.retval) { + console.error('Error: Unknown hash in Downloads: ', hash); + return; + } + fileStat.info.isSearched = isNew ? true : Downloads.statusMap[hash].isSearched; + Downloads.statusMap[hash] = fileStat.info; + } + ); + }, }; function InvalidFileMessage() { @@ -75,33 +69,39 @@ function InvalidFileMessage() { function addFile(url) { // valid url format: retroshare://file?name=...&size=...&hash=... - if(!url.startsWith('retroshare://')) { + if (!url.startsWith('retroshare://')) { InvalidFileMessage(); return; } - let details = m.parseQueryString(url.split('?')[1]); - if(!details.hasOwnProperty('name') || - !details.hasOwnProperty('size') || - !details.hasOwnProperty('hash')) { + const details = m.parseQueryString(url.split('?')[1]); + if ( + !Object.prototype.hasOwnProperty.call(details, 'name') || + !Object.prototype.hasOwnProperty.call(details, 'size') || + !Object.prototype.hasOwnProperty.call(details, 'hash') + ) { InvalidFileMessage(); return; } - rs.rsJsonApiRequest('/rsFiles/FileRequest', { - fileName: details.name, - hash: details.hash, - flags: util.RS_FILE_REQ_ANONYMOUS_ROUTING, - size: Number.parseInt(details.size), - }, (status) => { - widget.popupMessage([ - m('i.fas.fa-file-medical'), - m('h3', 'Add new file'), - m('hr'), - m('p', 'Successfully added file!'), - ]); - }) -}; + rs.rsJsonApiRequest( + '/rsFiles/FileRequest', + { + fileName: details.name, + hash: details.hash, + flags: util.RS_FILE_REQ_ANONYMOUS_ROUTING, + size: Number.parseInt(details.size), + }, + (status) => { + widget.popupMessage([ + m('i.fas.fa-file-medical'), + m('h3', 'Add new file'), + m('hr'), + m('p', 'Successfully added file!'), + ]); + } + ); +} -NewFileDialog = () => { +const NewFileDialog = () => { let url = ''; return { view: () => [ @@ -110,11 +110,15 @@ NewFileDialog = () => { m('hr'), m('p', 'Enter the file link:'), m('input[type=text][name=fileurl]', { - onchange: (e) => url = e.target.value, + onchange: (e) => (url = e.target.value), }), - m('button', { - onclick: () => addFile(url), - }, 'Add'), + m( + 'button', + { + onclick: () => addFile(url), + }, + 'Add' + ), ], }; }; @@ -122,38 +126,42 @@ NewFileDialog = () => { const Component = () => { return { oninit: () => { - rs.setBackgroundTask( - Downloads.loadStatus, - 1000, - () => { - return (m.route.get() === '/files/files') - } - ); + rs.setBackgroundTask(Downloads.loadStatus, 1000, () => { + return m.route.get() === '/files/files'; + }); Downloads.resetSearch(); }, - view: () => m('.widget', [ - m('h3', 'Downloads (' + Downloads.hashes.length +' files)'), - m('hr'), - m('button', { - onclick: () => widget.popupMessage(m(NewFileDialog)), - }, 'Add new file'), - m('button', { - onclick: () => rs.rsJsonApiRequest( - '/rsFiles/FileClearCompleted'), - }, 'Clear completed'), - Object.keys(Downloads.statusMap).map( - (hash) => m(util.File, { - info: Downloads.statusMap[hash], - direction: 'down', - transferred: Downloads.statusMap[hash].transfered.xint64, - parts: [], - })), - ]), + view: () => + m('.widget', [ + m('h3', 'Downloads (' + Downloads.hashes.length + ' files)'), + m('hr'), + m( + 'button', + { + onclick: () => widget.popupMessage(m(NewFileDialog)), + }, + 'Add new file' + ), + m( + 'button', + { + onclick: () => rs.rsJsonApiRequest('/rsFiles/FileClearCompleted'), + }, + 'Clear completed' + ), + Object.keys(Downloads.statusMap).map((hash) => + m(util.File, { + info: Downloads.statusMap[hash], + direction: 'down', + transferred: Downloads.statusMap[hash].transfered.xint64, + parts: [], + }) + ), + ]), }; }; module.exports = { Component, - list: Downloads.statusMap + list: Downloads.statusMap, }; - diff --git a/webui-src/app/files/files_resolver.js b/webui-src/app/files/files_resolver.js index 05d905f..b8c3295 100644 --- a/webui-src/app/files/files_resolver.js +++ b/webui-src/app/files/files_resolver.js @@ -1,12 +1,10 @@ -let m = require('mithril'); -let rs = require('rswebui'); -let widget = require('widgets'); - -let downloads = require('files/files_downloads'); -let uploads = require('files/files_uploads'); -let util = require('files/files_util'); -let search = require('files/files_search'); +const m = require('mithril'); +const widget = require('widgets'); +const downloads = require('files/files_downloads'); +const uploads = require('files/files_uploads'); +const util = require('files/files_util'); +const search = require('files/files_search'); const MyFiles = () => { return { @@ -20,19 +18,20 @@ const MyFiles = () => { }; }; -let sections = { +const sections = { files: MyFiles, - search: search, + search, }; const Layout = { - view: (vnode) => m('.tab-page', [ - m(widget.Sidebar, { - tabs: Object.keys(sections), - baseRoute: '/files/', - }), - m('.node-panel', vnode.children), - ]), + view: (vnode) => + m('.tab-page', [ + m(widget.Sidebar, { + tabs: Object.keys(sections), + baseRoute: '/files/', + }), + m('.node-panel', vnode.children), + ]), }; module.exports = { @@ -41,4 +40,3 @@ module.exports = { return m(Layout, m(sections[tab])); }, }; - diff --git a/webui-src/app/files/files_search.js b/webui-src/app/files/files_search.js index 0971961..9ec4d61 100644 --- a/webui-src/app/files/files_search.js +++ b/webui-src/app/files/files_search.js @@ -1,37 +1,36 @@ -let m = require('mithril'); -let rs = require('rswebui'); -let util = require('files/files_util') - +const m = require('mithril'); +const rs = require('rswebui'); let matchString = ''; const SearchBar = () => { return { - view: () => m('input[type=text][placeholder=search]', { - value: matchString, - oninput: (e) => matchString = e.target.value, - onchange: (e) => { - console.log('searching for string: ', matchString); - //let source = new EventSource( - // 'http://127.0.0.1:9092/rsFiles/turtleSearchRequest', { - // withCredentials: true - // }); - rs.rsJsonApiRequest('/rsfiles/turtleSearchRequest', { - matchString - }, (data, stat) => { - console.log('got: ', stat, ' data: ', data); - }) - }, - }), + view: () => + m('input[type=text][placeholder=search]', { + value: matchString, + oninput: (e) => (matchString = e.target.value), + onchange: (e) => { + console.log('searching for string: ', matchString); + // let source = new EventSource( + // 'http://127.0.0.1:9092/rsFiles/turtleSearchRequest', { + // withCredentials: true + // }); + rs.rsJsonApiRequest( + '/rsfiles/turtleSearchRequest', + { + matchString, + }, + (data, stat) => { + console.log('got: ', stat, ' data: ', data); + } + ); + }, + }), }; }; const Layout = () => { return { - view: vnode => m('.widget', [ - m('h3', 'Search'), - m('hr'), - m(SearchBar), - ]), + view: (vnode) => m('.widget', [m('h3', 'Search'), m('hr'), m(SearchBar)]), }; }; @@ -40,4 +39,3 @@ module.exports = { return m(Layout); }, }; - diff --git a/webui-src/app/files/files_uploads.js b/webui-src/app/files/files_uploads.js index 4afa0eb..7a301f6 100644 --- a/webui-src/app/files/files_uploads.js +++ b/webui-src/app/files/files_uploads.js @@ -1,93 +1,89 @@ -let m = require('mithril'); -let rs = require('rswebui'); -let util = require('files/files_util') +const m = require('mithril'); +const rs = require('rswebui'); +const util = require('files/files_util'); - -function updateFileDetail(hash, isNew = false) { - rs.rsJsonApiRequest( - '/rsFiles/FileDetails', { - hash, - hintflags: 32, // RS_FILE_HINTS_UPLOAD - }, - (fileStat) => { - if(!fileStat.retval) { - console.error('Error: Unknown hash in Uploads: ', hash); - return; - } - fileStat.info.isSearched = (isNew ? - true : - Uploads.statusMap[hash].isSearched); - Uploads.statusMap[hash] = fileStat.info; - }, - ); -} - -let Uploads = { +const Uploads = { statusMap: {}, hashes: [], loadHashes() { - rs.rsJsonApiRequest( - '/rsFiles/FileUploads', {}, - (d) => Uploads.hashes = d.hashs, - ); + rs.rsJsonApiRequest('/rsFiles/FileUploads', {}, (d) => (Uploads.hashes = d.hashs)); }, loadStatus() { Uploads.loadHashes(); - let fileKeys = Object.keys(Uploads.statusMap); - if(Uploads.hashes.length !== fileKeys.length) { + const fileKeys = Object.keys(Uploads.statusMap); + if (Uploads.hashes.length !== fileKeys.length) { // New file added - if(Uploads.hashes.length > fileKeys.length) { - let newHashes = util.compareArrays(Uploads.hashes, fileKeys); - for(let hash of newHashes) { - updateFileDetail(hash, true); + if (Uploads.hashes.length > fileKeys.length) { + const newHashes = util.compareArrays(Uploads.hashes, fileKeys); + for (const hash of newHashes) { + Uploads.updateFileDetail(hash, true); } } // Existing file removed else { - let oldHashes = util.compareArrays(fileKeys, Uploads.hashes); - for(let hash of oldHashes) { + const oldHashes = util.compareArrays(fileKeys, Uploads.hashes); + for (const hash of oldHashes) { delete Uploads.statusMap[hash]; } } } - for(let hash in Uploads.statusMap) { - updateFileDetail(hash); + for (const hash in Uploads.statusMap) { + Uploads.updateFileDetail(hash); } }, + updateFileDetail(hash, isNew = false) { + rs.rsJsonApiRequest( + '/rsFiles/FileDetails', + { + hash, + hintflags: 32, // RS_FILE_HINTS_UPLOAD + }, + (fileStat) => { + if (!fileStat.retval) { + console.error('Error: Unknown hash in Uploads: ', hash); + return; + } + fileStat.info.isSearched = isNew ? true : Uploads.statusMap[hash].isSearched; + Uploads.statusMap[hash] = fileStat.info; + } + ); + }, }; -function averageOf(peers){ - return peers.reduce( (s,e) => s + e.transfered.xint64, 0) / peers.length; +function averageOf(peers) { + return peers.reduce((s, e) => s + e.transfered.xint64, 0) / peers.length; } const Component = () => { return { - oninit: () => rs.setBackgroundTask( - Uploads.loadStatus, - 1000, - () => { - return (m.route.get() === '/files/files') - } - ), - view: () => (Uploads.hashes.length > 0 ? m('.widget', [ - m('h3', 'Uploads ('+ Uploads.hashes.length +' files)'), - m('hr'), - Object.keys(Uploads.statusMap).map( - (hash) => (m(util.File, { - info: Uploads.statusMap[hash], - direction: 'up', - transferred: averageOf(Uploads.statusMap[hash].peers), - parts: Uploads.statusMap[hash].peers.reduce((a,e) => [...a, e.transfered.xint64], []), - })) - ), - ]) : []) + oninit: () => + rs.setBackgroundTask(Uploads.loadStatus, 1000, () => { + return m.route.get() === '/files/files'; + }), + view: () => + Uploads.hashes.length > 0 + ? m('.widget', [ + m('h3', 'Uploads (' + Uploads.hashes.length + ' files)'), + m('hr'), + Object.keys(Uploads.statusMap).map((hash) => + m(util.File, { + info: Uploads.statusMap[hash], + direction: 'up', + transferred: averageOf(Uploads.statusMap[hash].peers), + parts: Uploads.statusMap[hash].peers.reduce( + (a, e) => [...a, e.transfered.xint64], + [] + ), + }) + ), + ]) + : [], }; }; module.exports = { Component, - list: Uploads.statusMap + list: Uploads.statusMap, }; - diff --git a/webui-src/app/files/files_util.js b/webui-src/app/files/files_util.js index 2a32afe..fec21ee 100644 --- a/webui-src/app/files/files_util.js +++ b/webui-src/app/files/files_util.js @@ -1,5 +1,5 @@ -let m = require('mithril'); -let rs = require('rswebui'); +const m = require('mithril'); +const rs = require('rswebui'); const RS_FILE_CTRL_PAUSE = 0x00000100; const RS_FILE_CTRL_START = 0x00000200; @@ -14,146 +14,191 @@ const FT_STATE_QUEUED = 0x0005; const FT_STATE_PAUSED = 0x0006; const FT_STATE_CHECKING_HASH = 0x0007; -const RS_FILE_REQ_ANONYMOUS_ROUTING = 0x00000040 +const RS_FILE_REQ_ANONYMOUS_ROUTING = 0x00000040; function makeFriendlyUnit(bytes) { let cnt = bytes; - for (s of ['','k','M','G']) { + for (const s of ['', 'k', 'M', 'G']) { if (cnt < 1000) { - return cnt.toFixed(1) + ' ' + s + 'B'; + return cnt.toFixed(1) + ' ' + s + 'B'; } else { - cnt = cnt / 1024; + cnt = cnt / 1024; } } return cnt.toFixed(1) + 'TB'; } function calcRemainingTime(bytes, rate) { - if (rate <=0 || bytes < 1) { - return '--'; - } else { - let secs = bytes / rate / 1024; - if (secs < 60) { - return secs.toFixed() + 's'; - } - let mins = secs / 60; secs = secs - Math.floor(mins) * 60; - if (mins < 60) { - return mins.toFixed() +'m ' + secs.toFixed() + 's'; - } - let hours = mins / 60; mins = mins -Math.floor(hours) * 60; - if (hours < 24) { - return hours.toFixed() +'h ' + mins.toFixed() + 'm'; - } - let days = hours / 24; hours = hours - Math.floor(days) * 24; - return days.toFixed() + 'd ' + hours.toFixed() + 'h'; + if (rate <= 0 || bytes < 1) { + return '--'; + } else { + let secs = bytes / rate / 1024; + if (secs < 60) { + return secs.toFixed() + 's'; } + let mins = secs / 60; + secs = secs - Math.floor(mins) * 60; + if (mins < 60) { + return mins.toFixed() + 'm ' + secs.toFixed() + 's'; + } + let hours = mins / 60; + mins = mins - Math.floor(hours) * 60; + if (hours < 24) { + return hours.toFixed() + 'h ' + mins.toFixed() + 'm'; + } + const days = hours / 24; + hours = hours - Math.floor(days) * 24; + return days.toFixed() + 'd ' + hours.toFixed() + 'h'; + } } function fileAction(hash, action) { - let action_header = ''; - let json_params = { + let actionHeader = ''; + const jsonParams = { hash, - flags: 0 + flags: 0, }; switch (action) { case 'cancel': - action_header = '/rsFiles/FileCancel'; + actionHeader = '/rsFiles/FileCancel'; break; case 'pause': - action_header = '/rsFiles/FileControl'; - json_params.flags = RS_FILE_CTRL_PAUSE; + actionHeader = '/rsFiles/FileControl'; + jsonParams.flags = RS_FILE_CTRL_PAUSE; break; case 'resume': - action_header = '/rsFiles/FileControl'; - json_params.flags = RS_FILE_CTRL_START; + actionHeader = '/rsFiles/FileControl'; + jsonParams.flags = RS_FILE_CTRL_START; break; case 'force_check': - action_header = '/rsFiles/FileControl'; - json_params.flags = RS_FILE_CTRL_FORCE_CHECK; + actionHeader = '/rsFiles/FileControl'; + jsonParams.flags = RS_FILE_CTRL_FORCE_CHECK; break; default: console.error('Unknown action in Downloads.control()'); return; - }; - rs.rsJsonApiRequest(action_header, json_params, () => {}); -}; + } + rs.rsJsonApiRequest(actionHeader, jsonParams, () => {}); +} function actionButton(file, action) { switch (action) { case 'resume': - return m('button', { - title: 'resume', - onclick: function() { - fileAction(file.hash, 'resume'); + return m( + 'button', + { + title: 'resume', + onclick() { + fileAction(file.hash, 'resume'); + }, }, - }, m('i.fas.fa-play')); + m('i.fas.fa-play') + ); case 'pause': - return m('button', { - title: 'pause', - onclick: function() { - fileAction(file.hash, 'pause'); + return m( + 'button', + { + title: 'pause', + onclick() { + fileAction(file.hash, 'pause'); + }, }, - }, m('i.fas.fa-pause')); + m('i.fas.fa-pause') + ); case 'cancel': - return m('button.red', { - title: 'cancel', - onclick: function() { - fileAction(file.hash, 'cancel'); + return m( + 'button.red', + { + title: 'cancel', + onclick() { + fileAction(file.hash, 'cancel'); + }, }, - }, m('i.fas.fa-times')); + m('i.fas.fa-times') + ); } -}; +} const ProgressBar = () => { return { - view: (v) => m('.progressbar', { - style: { - content: v.attrs.rate + '%' - } - }, - m('span.progress-status', { - style: { - width: v.attrs.rate + '%' - } - }, v.attrs.rate.toPrecision(3) + '%')) + view: (v) => + m( + '.progressbar', + { + style: { + content: v.attrs.rate + '%', + }, + }, + m( + 'span.progress-status', + { + style: { + width: v.attrs.rate + '%', + }, + }, + v.attrs.rate.toPrecision(3) + '%' + ) + ), }; }; const File = () => { return { - view: (v) => m('.file-view', { - key: v.attrs.info.hash, - style: { - display: v.attrs.info.isSearched ? "block" : "none", - } - }, [ - m('p', v.attrs.info.fname), - (v.attrs.direction === 'up' || v.attrs.info.downloadStatus === FT_STATE_COMPLETE) ? [] : [ - actionButton(v.attrs.info, 'cancel'), - actionButton(v.attrs.info, - v.attrs.info.downloadStatus === FT_STATE_PAUSED ? - 'resume' : 'pause'), - ], - v.attrs.direction === 'up' ? [] : - m(ProgressBar, { - rate: v.attrs.transferred / v.attrs.info.size.xint64 * 100, - }), - m('span.filestat', m('i.fas.fa-file'), makeFriendlyUnit( - v.attrs.info.size.xint64)), - m('span.filestat', m('i.fas.fa-arrow-circle-' + v.attrs.direction), - makeFriendlyUnit(v.attrs.info.tfRate * 1024) + '/s'), - v.attrs.direction === 'up' ? [] : - m('span.filestat', {title: 'time remaining'}, [ m('i.fas.fa-clock'), - calcRemainingTime( v.attrs.info.size.xint64 - v.attrs.transferred, v.attrs.info.tfRate)]), - m('span.filestat', {title: 'peers'}, [m('i.fas.fa-users'), v.attrs.info.peers.length], v.attrs.parts.reduce((a,e) => [...a, ' - ' + makeFriendlyUnit(e)],[])), - ]), - } + view: (v) => + m( + '.file-view', + { + key: v.attrs.info.hash, + style: { + display: v.attrs.info.isSearched ? 'block' : 'none', + }, + }, + [ + m('p', v.attrs.info.fname), + v.attrs.direction === 'up' || v.attrs.info.downloadStatus === FT_STATE_COMPLETE + ? [] + : [ + actionButton(v.attrs.info, 'cancel'), + actionButton( + v.attrs.info, + v.attrs.info.downloadStatus === FT_STATE_PAUSED ? 'resume' : 'pause' + ), + ], + v.attrs.direction === 'up' + ? [] + : m(ProgressBar, { + rate: (v.attrs.transferred / v.attrs.info.size.xint64) * 100, + }), + m('span.filestat', m('i.fas.fa-file'), makeFriendlyUnit(v.attrs.info.size.xint64)), + m( + 'span.filestat', + m('i.fas.fa-arrow-circle-' + v.attrs.direction), + makeFriendlyUnit(v.attrs.info.tfRate * 1024) + '/s' + ), + v.attrs.direction === 'up' + ? [] + : m('span.filestat', { title: 'time remaining' }, [ + m('i.fas.fa-clock'), + calcRemainingTime( + v.attrs.info.size.xint64 - v.attrs.transferred, + v.attrs.info.tfRate + ), + ]), + m( + 'span.filestat', + { title: 'peers' }, + [m('i.fas.fa-users'), v.attrs.info.peers.length], + v.attrs.parts.reduce((a, e) => [...a, ' - ' + makeFriendlyUnit(e)], []) + ), + ] + ), + }; }; const SearchBar = () => { @@ -164,9 +209,8 @@ const SearchBar = () => { value: searchString, oninput: (e) => { searchString = e.target.value.toLowerCase(); - for(let hash in v.attrs.list) { - if(v.attrs.list[hash].fname.toLowerCase().indexOf( - searchString) > -1) { + for (const hash in v.attrs.list) { + if (v.attrs.list[hash].fname.toLowerCase().indexOf(searchString) > -1) { v.attrs.list[hash].isSearched = true; } else { v.attrs.list[hash].isSearched = false; @@ -181,10 +225,10 @@ function compareArrays(big, small) { // Use filter on bigger array // Pass `new Set(array_to_compare)` as second param to filter // Source: https://stackoverflow.com/a/40538072/7683374 - return big.filter(function(val) { + return big.filter(function (val) { return !this.has(val); }, new Set(small)); -}; +} module.exports = { RS_FILE_CTRL_PAUSE, @@ -204,4 +248,3 @@ module.exports = { SearchBar, compareArrays, }; - diff --git a/webui-src/app/green-black.scss b/webui-src/app/green-black.scss index 23fd241..c692f48 100644 --- a/webui-src/app/green-black.scss +++ b/webui-src/app/green-black.scss @@ -1,233 +1,242 @@ body { - background-color: azure; - color: black; - font-family: monospace; - margin: 0em; - /*padding: 1.5em;*/ - padding: 2mm; - font-size: 0.9em; - box-sizing: border-box; + background-color: azure; + color: black; + font-family: monospace; + margin: 0em; + /*padding: 1.5em;*/ + padding: 2mm; + font-size: 0.9em; + box-sizing: border-box; } -#overlay{ - z-index: 10; - position: fixed; - top:0; - left:0; - width: 100%; - height: 100%; - background-color: rgba(0.5,0.5,0.5,0.8); +#overlay { + z-index: 10; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0.5, 0.5, 0.5, 0.8); } -.paddingbox{ - padding:2mm; +.paddingbox { + padding: 2mm; } -.nav{ - list-style-type: none; - padding: 0em; - margin: 0em; +.nav { + list-style-type: none; + padding: 0em; + margin: 0em; } -.nav li{ - display: inline; - padding: 0.1em; - margin-right: 1em; - border-width: 0.1em; - border-color: blue; - border-bottom-style: solid; - cursor: pointer; +.nav li { + display: inline; + padding: 0.1em; + margin-right: 1em; + border-width: 0.1em; + border-color: blue; + border-bottom-style: solid; + cursor: pointer; } -td{ - padding: 0.3em; - border-style: solid; - border-width: 0.1em; - border-color: black; +td { + padding: 0.3em; + border-style: solid; + border-width: 0.1em; + border-color: black; } hr { - color: black; + color: black; } -.menu{ - border-style: solid; - border-color: black; - border-width: 0.1em; - cursor: pointer; - padding: 0.0em; +.menu { + border-style: solid; + border-color: black; + border-width: 0.1em; + cursor: pointer; + padding: 0em; } -.btn{ - border-style: solid; - border-color: black; - border-width: 0.1em; - cursor: pointer; - padding: 0.1em; +.btn { + border-style: solid; + border-color: black; + border-width: 0.1em; + cursor: pointer; + padding: 0.1em; } -.btn2, .box{ - border-style: solid; - /*border-color: black;*/ - border-color: black; - /*border-width: 1px;*/ - border-radius: 0mm; - padding: 2mm; - font-size: 10mm; - cursor: pointer; - margin-bottom: 2mm; +.btn2, +.box { + border-style: solid; + /*border-color: black;*/ + border-color: black; + /*border-width: 1px;*/ + border-radius: 0mm; + padding: 2mm; + font-size: 10mm; + cursor: pointer; + margin-bottom: 2mm; } -.btn2:hover{ - background-color: midnightblue; +.btn2:hover { + background-color: midnightblue; } -.btnSmall{ - border-style: solid; - /*border-color: black;*/ - border-color: black; - /*border-width: 1px;*/ - border-radius: 0mm; - padding: 1mm; - font-size: 100%; +.btnSmall { + border-style: solid; + /*border-color: black;*/ + border-color: black; + /*border-width: 1px;*/ + border-radius: 0mm; + padding: 1mm; + font-size: 100%; - cursor: pointer; - margin-bottom: 0mm; + cursor: pointer; + margin-bottom: 0mm; } -.hidden{ - display:none; +.hidden { + display: none; } -.noBorderTable{ - width: 100%; - border: none; - border-collapse: collapse; +.noBorderTable { + width: 100%; + border: none; + border-collapse: collapse; } -.noBorderTD{ - border: none; - border-collapse: collapse; - vertical-align: center; +.noBorderTD { + border: none; + border-collapse: collapse; + vertical-align: center; } -.filelink{ - color: inherit; +.filelink { + color: inherit; } +input, +textarea { + color: black; + font-family: monospace; + background-color: azure; + border-color: black; + font-size: 10mm; + border-radius: 0mm; + border-width: 1mm; + padding: 2mm; + margin-bottom: 1mm; + margin-right: 1mm; -input,textarea{ - color: black; - font-family: monospace; - background-color: azure; - border-color: black; - font-size: 10mm; - border-radius: 0mm; - border-width: 1mm; - padding: 2mm; - margin-bottom: 1mm; - margin-right: 1mm; - - /* make the button the whole screen width */ - width: 100%; - /* make the text input fit small screens*/ - box-sizing: border-box; + /* make the button the whole screen width */ + width: 100%; + /* make the text input fit small screens*/ + box-sizing: border-box; } -input:hover{ - background-color: midnightblue; +input:hover { + background-color: midnightblue; } -textarea#txtNewMsg{ - color: black; - font-family: monospace; - background-color: azure; - border-color: black; - font-size: 100%; - border-radius: 0mm; - border-width: 1mm; - padding: 2mm; - margin-bottom: 0mm; - margin-right: 1mm; - height:110px; - resize: none; - /* make the button the whole screen width */ - width: 100%; - /*height: 100%;*/ - /* make the text input fit small screens*/ - box-sizing: border-box; +textarea#txtNewMsg { + color: black; + font-family: monospace; + background-color: azure; + border-color: black; + font-size: 100%; + border-radius: 0mm; + border-width: 1mm; + padding: 2mm; + margin-bottom: 0mm; + margin-right: 1mm; + height: 110px; + resize: none; + /* make the button the whole screen width */ + width: 100%; + /*height: 100%;*/ + /* make the text input fit small screens*/ + box-sizing: border-box; } -textarea#certificate{ - color: black; - font-family: courrier; - background-color: azure; - border-color: black; - font-size: 100%; - border-radius: 0mm; - border-width: 1mm; - padding: 2mm; - margin-bottom: 0mm; - margin-right: 1mm; - height:110px; - resize: none; - /* make the button the whole screen width */ - width: 100%; - /*height: 100%;*/ - /* make the text input fit small screens*/ - box-sizing: border-box; +textarea#certificate { + color: black; + font-family: courrier; + background-color: azure; + border-color: black; + font-size: 100%; + border-radius: 0mm; + border-width: 1mm; + padding: 2mm; + margin-bottom: 0mm; + margin-right: 1mm; + height: 110px; + resize: none; + /* make the button the whole screen width */ + width: 100%; + /*height: 100%;*/ + /* make the text input fit small screens*/ + box-sizing: border-box; } -input#txtMsgKeyword{ - color: black; - font-family: monospace; - background-color: azure; - border-color: black; - font-size: 100%; - border-radius: 0mm; - border-width: 1mm; - padding: 1mm; - margin-bottom: 0mm; - margin-right: 1mm; +input#txtMsgKeyword { + color: black; + font-family: monospace; + background-color: azure; + border-color: black; + font-size: 100%; + border-radius: 0mm; + border-width: 1mm; + padding: 1mm; + margin-bottom: 0mm; + margin-right: 1mm; - /* make the button the whole screen width */ - width: 100%; - /* make the text input fit small screens*/ - box-sizing: border-box; + /* make the button the whole screen width */ + width: 100%; + /* make the text input fit small screens*/ + box-sizing: border-box; } .checkbox { - width: auto; + width: auto; } -.flexbox{ - display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */ - display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */ - display: -ms-flexbox; /* TWEENER - IE 10 */ - display: -webkit-flex; /* NEW - Chrome */ - display: flex; /* NEW, Spec - Opera 12.1, Firefox 20+ */ +.flexbox { + display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */ + display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */ + display: -ms-flexbox; /* TWEENER - IE 10 */ + display: -webkit-flex; /* NEW - Chrome */ + display: flex; /* NEW, Spec - Opera 12.1, Firefox 20+ */ } -.flexwidemember{ - -webkit-box-flex: 1; /* OLD - iOS 6-, Safari 3.1-6 */ - -moz-box-flex: 1; /* OLD - Firefox 19- */ - width: 20%; /* For old syntax, otherwise collapses. */ - -webkit-flex: 1; /* Chrome */ - -ms-flex: 1; /* IE 10 */ - flex: 1; /* NEW, Spec - Opera 12.1, Firefox 20+ */ +.flexwidemember { + -webkit-box-flex: 1; /* OLD - iOS 6-, Safari 3.1-6 */ + -moz-box-flex: 1; /* OLD - Firefox 19- */ + width: 20%; /* For old syntax, otherwise collapses. */ + -webkit-flex: 1; /* Chrome */ + -ms-flex: 1; /* IE 10 */ + flex: 1; /* NEW, Spec - Opera 12.1, Firefox 20+ */ } -#logo_splash{ - -webkit-animation-fill-mode: forwards; /* Chrome, Safari, Opera */ - animation-fill-mode: forwards; - -webkit-animation-name: logo_splash; /* Chrome, Safari, Opera */ - -webkit-animation-duration: 3s; /* Chrome, Safari, Opera */ - animation-name: logo_splash; - animation-duration: 3s; - text-align: center; +#logo_splash { + -webkit-animation-fill-mode: forwards; /* Chrome, Safari, Opera */ + animation-fill-mode: forwards; + -webkit-animation-name: logo_splash; /* Chrome, Safari, Opera */ + -webkit-animation-duration: 3s; /* Chrome, Safari, Opera */ + animation-name: logo_splash; + animation-duration: 3s; + text-align: center; } /* Chrome, Safari, Opera */ @-webkit-keyframes logo_splash { - from {opacity: 0;} - to {opacity: 1;} + from { + opacity: 0; + } + to { + opacity: 1; + } } /* Standard syntax */ @keyframes logo_splash { - from {opacity: 0;} - to {opacity: 1;} + from { + opacity: 0; + } + to { + opacity: 1; + } } diff --git a/webui-src/app/home.js b/webui-src/app/home.js index 9d16a95..1e5ab39 100644 --- a/webui-src/app/home.js +++ b/webui-src/app/home.js @@ -1,23 +1,21 @@ -let m = require('mithril'); -let rs = require('rswebui'); -let widget = require('widgets'); +const m = require('mithril'); +const rs = require('rswebui'); +const widget = require('widgets'); -let Certificate = () => { +const Certificate = () => { let ownCert = ''; let short = false; function loadOwnCert(short) { if (short) { - rs.rsJsonApiRequest('/rsPeers/GetShortInvite', - {formatRadix: true}, - data => (ownCert = data.invite), + rs.rsJsonApiRequest( + '/rsPeers/GetShortInvite', + { formatRadix: true }, + (data) => (ownCert = data.invite) ); } else { - rs.rsJsonApiRequest('/rsPeers/GetRetroshareInvite', - {}, - data => (ownCert = data.retval), - ); + rs.rsJsonApiRequest('/rsPeers/GetRetroshareInvite', {}, (data) => (ownCert = data.retval)); } - }; + } return { oninit() { @@ -30,7 +28,8 @@ let Certificate = () => { m('h3', 'Certificate'), m('p', 'Your Retroshare certificate, click to copy'), m('hr'), - m('textarea[readonly]', + m( + 'textarea[readonly]', { id: 'certificate', rows: 14, @@ -41,26 +40,23 @@ let Certificate = () => { document.execCommand('copy'); }, }, - ownCert, + ownCert ), m('input[type=checkbox]', { checked: short, - oninput: e => { + oninput: (e) => { short = e.target.checked; loadOwnCert(short); }, - }), 'Short version', + }), + 'Short version', ]); }, }; }; function invalidCertPrompt() { - widget.popupMessage([ - m('h3', 'Error'), - m('hr'), - m('p', 'Not a valid Retroshare certificate.'), - ]); + widget.popupMessage([m('h3', 'Error'), m('hr'), m('p', 'Not a valid Retroshare certificate.')]); } function confirmAddPrompt(details, cert) { @@ -72,40 +68,35 @@ function confirmAddPrompt(details, cert) { m('ul', [ m('li', 'Name: ' + details.name), m('li', 'Location: ' + details.location + '(' + details.id + ')'), - m('li', - details.isHiddenNode ? details.hiddenNodeAddress : details.extAddr, - ), + m('li', details.isHiddenNode ? details.hiddenNodeAddress : details.extAddr), ]), - m('button', + m( + 'button', { onclick: () => - rs.rsJsonApiRequest( - '/rsPeers/loadCertificateFromString', - {cert}, - data => { - if (data.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.'), - ]); - } - }, - ), + rs.rsJsonApiRequest('/rsPeers/loadCertificateFromString', { cert }, (data) => { + if (data.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' ), ]); } function addFriendFromCert(cert) { - rs.rsJsonApiRequest('/rsPeers/loadDetailsFromStringCert', {cert}, data => { + rs.rsJsonApiRequest('/rsPeers/loadDetailsFromStringCert', { cert }, (data) => { if (!data.retval) { invalidCertPrompt(); return null; @@ -123,8 +114,8 @@ const AddFriend = () => { // TODO handle incorrect file return null; } - let reader = new FileReader(); - reader.onload = e => { + const reader = new FileReader(); + reader.onload = (e) => { certificate = e.target.result; m.redraw(); }; @@ -132,14 +123,16 @@ const AddFriend = () => { } return { - view: vnode => + view: (vnode) => m('.widget.widget-half', [ m('h3', 'Add friend'), - m('p', - 'Did you recieve a certificate from a friend? You can also drag and drop the file below', + m( + 'p', + 'Did you recieve a certificate from a friend? You can also drag and drop the file below' ), m('hr'), - m('.cert-drop-zone', + m( + '.cert-drop-zone', { isDragged: false, ondragenter: () => (vnode.state.isDragged = true), @@ -152,8 +145,8 @@ const AddFriend = () => { } : {}, - ondragover: e => e.preventDefault(), - ondrop: e => { + ondragover: (e) => e.preventDefault(), + ondrop: (e) => { vnode.state.isDragged = false; e.preventDefault(); loadFileContents(e.target.files || e.dataTransfer.files); @@ -162,23 +155,24 @@ const AddFriend = () => { [ m('input[type=file][name=certificate]', { - onchange: e => { + onchange: (e) => { // Note: this one is for the 'browse' button loadFileContents(e.target.files || e.dataTransfer.files); }, }), 'Or paste the certificate here', m('textarea[rows=5][style="width: 90%; display: block;"]', { - oninput: e => (certificate = e.target.value), + oninput: (e) => (certificate = e.target.value), value: certificate, }), - m('button', + m( + 'button', { onclick: () => addFriendFromCert(certificate), }, - 'Add', + 'Add' ), - ], + ] ), ]), }; @@ -191,4 +185,3 @@ const Layout = () => { }; module.exports = Layout; - diff --git a/webui-src/app/login.js b/webui-src/app/login.js index fd486f6..8d902ed 100644 --- a/webui-src/app/login.js +++ b/webui-src/app/login.js @@ -1,97 +1,120 @@ const m = require('mithril'); const rs = require('rswebui'); -const displayErrorMessage = function(message) { +const displayErrorMessage = function (message) { m.render(document.getElementById('error'), message); }; -const verifyLogin = async function(uname, passwd, url, displayAuthError = true) { +const verifyLogin = async function (uname, passwd, url, displayAuthError = true) { const loginHeader = { - Authorization: 'Basic ' + btoa(uname + ':' + passwd) + Authorization: 'Basic ' + btoa(uname + ':' + passwd), }; if (!url.trim()) { - displayErrorMessage("Server-url is missing, please enter json-api url"); + displayErrorMessage('Server-url is missing, please enter json-api url'); return; } rs.setKeys('', '', url, false); - rs.logon(loginHeader, displayAuthError ? displayErrorMessage : () => {}, displayErrorMessage, () => { - rs.setKeys(uname, passwd, url); - m.route.set('/home'); - }); + rs.logon( + loginHeader, + displayAuthError ? displayErrorMessage : () => {}, + displayErrorMessage, + () => { + rs.setKeys(uname, passwd, url); + m.route.set('/home'); + } + ); }; function loginComponent() { - var urlParams = new URLSearchParams(window.location.search); - let uname = urlParams.get('Username')||'webui'; - let passwd = urlParams.get('Password') ||''; - let url = 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',''); + const urlParams = new URLSearchParams(window.location.search); + let uname = urlParams.get('Username') || 'webui'; + let passwd = urlParams.get('Password') || ''; + let url = + 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', ''); let withOptions = false; - let logo = () => m('img.logo[width=30%]', { - src: '../data/retroshare.svg', - alt: 'retroshare_icon' - }); - let inputName = () => m('input', { - id: 'username', - type: 'text', - value: uname, - placeholder: 'Username', - onchange: e => (uname = e.target.value) - }); - let inputPassword = () => m('input[autofocus]', { - id: 'password', - type: 'password', - placeholder: 'Password', - onchange: e => (passwd = e.target.value), - onkeydown: e => { - if (e.keyCode===13) { - passwd = e.target.value; - loginBtn.click(); - return false; - } - return true; - } - }); - let inputUrl = () => m('input',{ - id: 'url', - type: 'text', - placeholder: 'Url', - value: url, - oninput: e => (url = e.target.value) - }); + const logo = () => + m('img.logo[width=30%]', { + src: '../data/retroshare.svg', + alt: 'retroshare_icon', + }); + const inputName = () => + m('input', { + id: 'username', + type: 'text', + value: uname, + placeholder: 'Username', + onchange: (e) => (uname = e.target.value), + }); + const buttonLogin = () => + m( + 'button.submit-btn', + { + id: 'loginBtn', + onclick: () => verifyLogin(uname, passwd, url), + }, + 'Login' + ); + const inputPassword = () => + m('input[autofocus]', { + id: 'password', + type: 'password', + placeholder: 'Password', + onchange: (e) => (passwd = e.target.value), + onkeydown: (e) => { + if (e.keyCode === 13) { + passwd = e.target.value; + buttonLogin().click(); + return false; + } + return true; + }, + }); + const inputUrl = () => + m('input', { + id: 'url', + type: 'text', + placeholder: 'Url', + value: url, + oninput: (e) => (url = e.target.value), + }); - let linkOptions = action => m('a',{ - onclick: e => withOptions=!withOptions, - }, action + ' options'); + const linkOptions = (action) => + m( + 'a', + { + onclick: (e) => (withOptions = !withOptions), + }, + action + ' options' + ); - let buttonLogin = () => m('button.submit-btn', { - id: 'loginBtn', - onclick: () => verifyLogin(uname, passwd, url) - }, 'Login'); - - let textError = () => m('p.error[id=error]'); + const textError = () => m('p.error[id=error]'); return { oninit: () => verifyLogin(uname, passwd, url, false), view: () => { - return m('.login-page', - m('.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(), - inputPassword(), - linkOptions('show'), - buttonLogin(), - textError() - ]) + return m( + '.login-page', + m( + '.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(), inputPassword(), linkOptions('show'), buttonLogin(), textError()] + ) ); - } + }, }; -}; +} module.exports = loginComponent; diff --git a/webui-src/app/mail/mail.css b/webui-src/app/mail/mail.css index 59863a5..63fde9d 100644 --- a/webui-src/app/mail/mail.css +++ b/webui-src/app/mail/mail.css @@ -1,58 +1,59 @@ /* star */ table.mails th:nth-child(1) { - width: 5%; - color: #fcba03; + width: 5%; + color: #fcba03; } /* attachments */ table.mails th:nth-child(2) { - width: 5%; - color: grey; + width: 5%; + color: grey; } /* subject */ table.mails th:nth-child(3) { - width: 50%; - text-align: start; + width: 50%; + text-align: start; } /* date */ table.mails th:nth-child(4) { - width: 40%; - text-align: start; + width: 40%; + text-align: start; } /* subject */ table.mails td:nth-child(3) { - text-align: start; - /* Truncate text with '...' */ - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + text-align: start; + /* Truncate text with '...' */ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } /* date */ table.mails td:nth-child(4) { - text-align: start; + text-align: start; } table.mails tr:hover { - background-color: #eef3f6; - cursor: pointer; + background-color: #eef3f6; + cursor: pointer; } table.mails tr.unread { - color: black; - background-color: #eef3f6; + color: black; + background-color: #eef3f6; } /* hide checkbox */ input.star-check { - display: none; + display: none; } /* use label with 'for' to manipulate checkbox */ input.star-check + label.star-check { - color: grey; -} input.star-check:checked + label.star-check { - color: #fcba03; + color: grey; +} +input.star-check:checked + label.star-check { + color: #fcba03; } iframe.msg { - border: 0; - width: 100%; - height: 450px; + border: 0; + width: 100%; + height: 450px; } diff --git a/webui-src/app/mail/mail_draftbox.js b/webui-src/app/mail/mail_draftbox.js index 43f6f9e..bbba56e 100644 --- a/webui-src/app/mail/mail_draftbox.js +++ b/webui-src/app/mail/mail_draftbox.js @@ -1,24 +1,27 @@ const m = require('mithril'); -const rs = require('rswebui'); const util = require('mail/mail_util'); - const Layout = () => { return { view: (v) => [ m('.widget', [ m('h3', 'Drafts'), m('hr'), - m(util.Table, m('tbody', v.attrs.list.map( - (msg) => m(util.MessageSummary, { - details: msg, - category: 'drafts', - }) - ))), + m( + util.Table, + m( + 'tbody', + v.attrs.list.map((msg) => + m(util.MessageSummary, { + details: msg, + category: 'drafts', + }) + ) + ) + ), ]), - ] + ], }; }; module.exports = Layout; - diff --git a/webui-src/app/mail/mail_inbox.js b/webui-src/app/mail/mail_inbox.js index 469d44e..a65e91c 100644 --- a/webui-src/app/mail/mail_inbox.js +++ b/webui-src/app/mail/mail_inbox.js @@ -1,24 +1,27 @@ const m = require('mithril'); -const rs = require('rswebui'); const util = require('mail/mail_util'); - const Layout = () => { return { view: (v) => [ m('.widget', [ m('h3', 'Inbox'), m('hr'), - m(util.Table, m('tbody', v.attrs.list.map( - (msg) => m(util.MessageSummary, { - details: msg, - category: 'inbox', - }) - ))), + m( + util.Table, + m( + 'tbody', + v.attrs.list.map((msg) => + m(util.MessageSummary, { + details: msg, + category: 'inbox', + }) + ) + ) + ), ]), - ] + ], }; }; module.exports = Layout; - diff --git a/webui-src/app/mail/mail_outbox.js b/webui-src/app/mail/mail_outbox.js index 42da072..b80b5ee 100644 --- a/webui-src/app/mail/mail_outbox.js +++ b/webui-src/app/mail/mail_outbox.js @@ -1,24 +1,27 @@ const m = require('mithril'); -const rs = require('rswebui'); const util = require('mail/mail_util'); - const Layout = () => { return { view: (v) => [ m('.widget', [ m('h3', 'Outbox'), m('hr'), - m(util.Table, m('tbody', v.attrs.list.map( - (msg) => m(util.MessageSummary, { - details: msg, - category: 'outbox', - }) - ))), + m( + util.Table, + m( + 'tbody', + v.attrs.list.map((msg) => + m(util.MessageSummary, { + details: msg, + category: 'outbox', + }) + ) + ) + ), ]), - ] + ], }; }; module.exports = Layout; - diff --git a/webui-src/app/mail/mail_resolver.js b/webui-src/app/mail/mail_resolver.js index 1fab764..5925456 100644 --- a/webui-src/app/mail/mail_resolver.js +++ b/webui-src/app/mail/mail_resolver.js @@ -1,8 +1,7 @@ -let m = require('mithril'); -let rs = require('rswebui'); -let util = require('mail/mail_util'); -let widget = require('widgets'); - +const m = require('mithril'); +const rs = require('rswebui'); +const util = require('mail/mail_util'); +const widget = require('widgets'); const Messages = { all: [], @@ -11,22 +10,25 @@ const Messages = { outbox: [], drafts: [], load() { - rs.rsJsonApiRequest('/rsMsgs/getMessageSummaries', {}, - (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); - }); + rs.rsJsonApiRequest('/rsMsgs/getMessageSummaries', {}, (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 + ); + }); }, -} +}; -let sections = { +const sections = { inbox: require('mail/mail_inbox'), outbox: require('mail/mail_outbox'), drafts: require('mail/mail_draftbox'), @@ -35,27 +37,33 @@ let sections = { const Layout = { oninit: Messages.load, - view: vnode => m('.tab-page', [ - m(widget.Sidebar, { - tabs: Object.keys(sections), - baseRoute: '/mail/', - }), - m('.node-panel', vnode.children), - ]) + view: (vnode) => + m('.tab-page', [ + m(widget.Sidebar, { + tabs: Object.keys(sections), + baseRoute: '/mail/', + }), + m('.node-panel', vnode.children), + ]), }; module.exports = { view: (v) => { const tab = v.attrs.tab; // TODO: utilize multiple routing params - if(v.attrs.hasOwnProperty('msgId')) { - return m(Layout, m(util.MessageView, { - id: v.attrs.msgId, - })) + if (Object.prototype.hasOwnProperty.call(v.attrs, 'msgId')) { + return m( + Layout, + m(util.MessageView, { + id: v.attrs.msgId, + }) + ); } - return m(Layout, m(sections[tab], { - list: Messages[tab], - })); + return m( + Layout, + m(sections[tab], { + list: Messages[tab], + }) + ); }, }; - diff --git a/webui-src/app/mail/mail_sentbox.js b/webui-src/app/mail/mail_sentbox.js index feada58..f13f4d4 100644 --- a/webui-src/app/mail/mail_sentbox.js +++ b/webui-src/app/mail/mail_sentbox.js @@ -1,24 +1,27 @@ const m = require('mithril'); -const rs = require('rswebui'); const util = require('mail/mail_util'); - const Layout = () => { return { view: (v) => [ m('.widget', [ m('h3', 'Sent'), m('hr'), - m(util.Table, m('tbody', v.attrs.list.map( - (msg) => m(util.MessageSummary, { - details: msg, - category: 'sent', - }) - ))), + m( + util.Table, + m( + 'tbody', + v.attrs.list.map((msg) => + m(util.MessageSummary, { + details: msg, + category: 'sent', + }) + ) + ) + ), ]), - ] + ], }; }; module.exports = Layout; - diff --git a/webui-src/app/mail/mail_util.js b/webui-src/app/mail/mail_util.js index b5fb3ea..ac01e55 100644 --- a/webui-src/app/mail/mail_util.js +++ b/webui-src/app/mail/mail_util.js @@ -1,6 +1,5 @@ const m = require('mithril'); const rs = require('rswebui'); -const people = require('people/people_util'); const RS_MSG_BOXMASK = 0x000f; @@ -13,61 +12,75 @@ const RS_MSG_NEW = 0x10; const RS_MSG_UNREAD_BY_USER = 0x40; const RS_MSG_STAR = 0x200; - const MessageSummary = () => { let details = {}; let files = []; let isStarred = undefined; let msgStatus = ''; return { - oninit: (v) => rs.rsJsonApiRequest('/rsMsgs/getMessage', { - msgId: v.attrs.details.msgId, - }, (data) => { - details = data.msg; - files = details.files; - isStarred = (details.msgflags & 0xf00) === RS_MSG_STAR; - let flag = details.msgflags & 0xf0; - if(flag === RS_MSG_NEW || flag == RS_MSG_UNREAD_BY_USER) { - msgStatus = 'unread'; - } else { - msgStatus = 'read'; - } - }), - view: (v) => m('tr.msgbody', { - key: details.msgId, - class: msgStatus, - onclick: () => m.route.set('/mail/:tab/:msgId', { - tab: v.attrs.category, - msgId: details.msgId, - }), - }, [ - m('td', - m('input.star-check[type=checkbox][id=msg-' + details.msgId + - ']', { - checked: v.state.isStarred - }), - // Use label with [for] to manipulate hidden checkbox - m('label.star-check[for=msg-' + details.msgId + ']', { - onclick: (e) => { - v.state.isStarred = !v.state.isStarred; - rs.rsJsonApiRequest('/rsMsgs/MessageStar', { + oninit: (v) => + rs.rsJsonApiRequest( + '/rsMsgs/getMessage', + { + msgId: v.attrs.details.msgId, + }, + (data) => { + details = data.msg; + files = details.files; + isStarred = (details.msgflags & 0xf00) === RS_MSG_STAR; + const flag = details.msgflags & 0xf0; + if (flag === RS_MSG_NEW || flag === RS_MSG_UNREAD_BY_USER) { + msgStatus = 'unread'; + } else { + msgStatus = 'read'; + } + } + ), + view: (v) => + m( + 'tr.msgbody', + { + key: details.msgId, + class: msgStatus, + onclick: () => + m.route.set('/mail/:tab/:msgId', { + tab: v.attrs.category, msgId: details.msgId, - mark: v.state.isStarred, - }); - // Stop event bubbling, both functions for supporting IE & FF - e.stopImmediatePropagation(); - e.preventDefault(); - }, - class: (details.msgflags & 0xf00) === RS_MSG_STAR ? - 'starred' : 'unstarred' - }, m('i.fas.fa-star'))), - m('td', files.length), - m('td', details.title), - //m('td', details.rspeerid_srcId == 0 ? - // '[Notification]' : - // people.getInfo(details.rspeerid_srcId)), - m('td', new Date(details.ts * 1000).toLocaleString()), - ]), + }), + }, + [ + m( + 'td', + m('input.star-check[type=checkbox][id=msg-' + details.msgId + ']', { + checked: isStarred, + }), + // Use label with [for] to manipulate hidden checkbox + m( + 'label.star-check[for=msg-' + details.msgId + ']', + { + onclick: (e) => { + isStarred = !isStarred; + rs.rsJsonApiRequest('/rsMsgs/MessageStar', { + msgId: details.msgId, + mark: isStarred, + }); + // Stop event bubbling, both functions for supporting IE & FF + e.stopImmediatePropagation(); + e.preventDefault(); + }, + class: (details.msgflags & 0xf00) === RS_MSG_STAR ? 'starred' : 'unstarred', + }, + m('i.fas.fa-star') + ) + ), + m('td', files.length), + m('td', details.title), + // m('td', details.rspeerid_srcId == 0 ? + // '[Notification]' : + // peopleUtils.getInfo(details.rspeerid_srcId)), // getInfo previously uses "/rsIdentity/getIdentitiesInfo" + m('td', new Date(details.ts * 1000).toLocaleString()), + ] + ), }; }; @@ -75,48 +88,68 @@ const MessageView = () => { let details = {}; let message = ''; return { - oninit: (v) => rs.rsJsonApiRequest('/rsMsgs/getMessage', { - msgId: v.attrs.id, - }, (data) => { - details = data.msg; - // regex to detect html tags - // better regex? /<[a-z][\s\S]*>/gi - if(/<\/*[a-z][^>]+?>/gi.test(details.msg)) { - message = details.msg; - } else { - message = '

' + details.msg + '

'; - } - }), - view: (v) => m('.widget.msgview', { - key: details.msgId, - }, [ - m('a[title=Back]', { - onclick: () => m.route.set('/mail/:tab', { - tab: m.route.param().tab, - }), - }, m('i.fas.fa-arrow-left')), - m('h3', details.title), - m('hr'), - m('iframe[title=message].msg', { - srcdoc: message, - }, message), - ]), + oninit: (v) => + rs.rsJsonApiRequest( + '/rsMsgs/getMessage', + { + msgId: v.attrs.id, + }, + (data) => { + details = data.msg; + // regex to detect html tags + // better regex? /<[a-z][\s\S]*>/gi + if (/<\/*[a-z][^>]+?>/gi.test(details.msg)) { + message = details.msg; + } else { + message = '

' + details.msg + '

'; + } + } + ), + view: (v) => + m( + '.widget.msgview', + { + key: details.msgId, + }, + [ + m( + 'a[title=Back]', + { + onclick: () => + m.route.set('/mail/:tab', { + tab: m.route.param().tab, + }), + }, + m('i.fas.fa-arrow-left') + ), + m('h3', details.title), + m('hr'), + m( + 'iframe[title=message].msg', + { + srcdoc: message, + }, + message + ), + ] + ), }; }; const Table = () => { return { oninit: (v) => {}, - view: (v) => m('table.mails', [ - m('tr', [ - m('th[title=starred]', m('i.fas.fa-star')), - m('th[title=attachments]', m('i.fas.fa-paperclip')), - m('th', 'Subject'), - //m('th', 'From'), - m('th', 'Date'), + view: (v) => + m('table.mails', [ + m('tr', [ + m('th[title=starred]', m('i.fas.fa-star')), + m('th[title=attachments]', m('i.fas.fa-paperclip')), + m('th', 'Subject'), + // m('th', 'From'), + m('th', 'Date'), + ]), + v.children, ]), - v.children, - ]), }; }; @@ -133,4 +166,3 @@ module.exports = { RS_MSG_UNREAD_BY_USER, RS_MSG_STAR, }; - diff --git a/webui-src/app/main.js b/webui-src/app/main.js index c5597fb..b4e342c 100644 --- a/webui-src/app/main.js +++ b/webui-src/app/main.js @@ -9,7 +9,6 @@ const mail = require('mail/mail_resolver'); const files = require('files/files_resolver'); const channels = require('channels/channels'); const config = require('config/config_resolver'); -const rs = require('rswebui'); const navIcon = { home: m('i.fas.fa-home'), @@ -19,32 +18,32 @@ const navIcon = { mail: m('i.fas.fa-envelope'), files: m('i.fas.fa-folder-open'), channels: m('i.fas.fa-tv'), - config: m('i.fas.fa-cogs') + config: m('i.fas.fa-cogs'), }; const navbar = () => { return { - view: vnode => + view: (vnode) => m( 'nav.tab-menu', Object.keys(vnode.attrs.links).map((linkName, i) => { - let active = m.route.get().split('/')[1] === linkName + const active = m.route.get().split('/')[1] === linkName; return m( m.route.Link, { href: vnode.attrs.links[linkName], - class: (active ? 'selected-tab-item' : '') + ' tab-menu-item', + class: (active ? 'selected-tab-item' : '') + ' tab-menu-item', }, [navIcon[linkName], linkName] ); }) ), - } + }; }; const Layout = () => { return { - view: vnode => + view: (vnode) => m('.content', [ m(navbar, { links: { @@ -55,49 +54,49 @@ const Layout = () => { mail: '/mail/inbox', files: '/files/files', channels: '/channels', - config: '/config/network' - } + config: '/config/network', + }, }), - m('#tab-content', vnode.children) - ]) + m('#tab-content', vnode.children), + ]), }; }; m.route(document.getElementById('main'), '/', { '/': { - render: () => m(login) + render: () => m(login), }, '/home': { - render: () => m(Layout, m(home)) + render: () => m(Layout, m(home)), }, '/network': { - render: () => m(Layout, m(network)) + render: () => m(Layout, m(network)), }, '/people': { - render: () => m(Layout, m(people)) + render: () => m(Layout, m(people)), }, '/chat/:lobby/:subaction': { - render: v => m(Layout, m(chat, v.attrs)) + render: (v) => m(Layout, m(chat, v.attrs)), }, '/chat/:lobby': { - render: v => m(Layout, m(chat, v.attrs)) + render: (v) => m(Layout, m(chat, v.attrs)), }, '/chat': { - render: () => m(Layout, m(chat)) + render: () => m(Layout, m(chat)), }, '/mail/:tab': { - render: v => m(Layout, m(mail, v.attrs)) + render: (v) => m(Layout, m(mail, v.attrs)), }, '/mail/:tab/:msgId': { - render: v => m(Layout, m(mail, v.attrs)) + render: (v) => m(Layout, m(mail, v.attrs)), }, '/files/:tab': { - render: v => m(Layout, m(files, v.attrs)) + render: (v) => m(Layout, m(files, v.attrs)), }, '/channels': { - render: () => m(Layout, m(channels)) + render: () => m(Layout, m(channels)), }, '/config/:tab': { - render: v => m(Layout, m(config, v.attrs)) - } + render: (v) => m(Layout, m(config, v.attrs)), + }, }); diff --git a/webui-src/app/mithril.js b/webui-src/app/mithril.js index 377e0a5..b60c1b2 100644 --- a/webui-src/app/mithril.js +++ b/webui-src/app/mithril.js @@ -1,1842 +1,2091 @@ -;(function() { -"use strict" -function Vnode(tag, key, attrs0, children0, text, dom) { - return {tag: tag, key: key, attrs: attrs0, children: children0, text: text, dom: dom, domSize: undefined, state: undefined, events: undefined, instance: undefined} -} -Vnode.normalize = function(node) { - if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined) - if (node == null || typeof node === "boolean") return null - if (typeof node === "object") return node - return Vnode("#", undefined, undefined, String(node), undefined, undefined) -} -Vnode.normalizeChildren = function(input) { - var children0 = [] - if (input.length) { - var isKeyed = input[0] != null && input[0].key != null - // Note: this is a *very* perf-sensitive check. - // Fun fact: merging the loop like this is somehow faster than splitting - // it, noticeably so. - for (var i = 1; i < input.length; i++) { - if ((input[i] != null && input[i].key != null) !== isKeyed) { - throw new TypeError("Vnodes must either always have keys or never have keys!") - } - } - for (var i = 0; i < input.length; i++) { - children0[i] = Vnode.normalize(input[i]) - } - } - return children0 -} -// Call via `hyperscriptVnode0.apply(startOffset, arguments)` -// -// The reason I do it this way, forwarding the arguments and passing the start -// offset in `this`, is so I don't have to create a temporary array in a -// performance-critical path. -// -// In native ES6, I'd instead add a final `...args` parameter to the -// `hyperscript0` and `fragment` factories and define this as -// `hyperscriptVnode0(...args)`, since modern engines do optimize that away. But -// ES5 (what Mithril requires thanks to IE support) doesn't give me that luxury, -// and engines aren't nearly intelligent enough to do either of these: -// -// 1. Elide the allocation for `[].slice.call(arguments, 1)` when it's passed to -// another function only to be indexed. -// 2. Elide an `arguments` allocation when it's passed to any function other -// than `Function.prototype.apply` or `Reflect.apply`. -// -// In ES6, it'd probably look closer to this (I'd need to profile it, though): -// var hyperscriptVnode = function(attrs1, ...children1) { -// if (attrs1 == null || typeof attrs1 === "object" && attrs1.tag == null && !Array.isArray(attrs1)) { -// if (children1.length === 1 && Array.isArray(children1[0])) children1 = children1[0] -// } else { -// children1 = children1.length === 0 && Array.isArray(attrs1) ? attrs1 : [attrs1, ...children1] -// attrs1 = undefined -// } -// -// if (attrs1 == null) attrs1 = {} -// return Vnode("", attrs1.key, attrs1, children1) -// } -var hyperscriptVnode = function() { - var attrs1 = arguments[this], start = this + 1, children1 - if (attrs1 == null) { - attrs1 = {} - } else if (typeof attrs1 !== "object" || attrs1.tag != null || Array.isArray(attrs1)) { - attrs1 = {} - start = this - } - if (arguments.length === start + 1) { - children1 = arguments[start] - if (!Array.isArray(children1)) children1 = [children1] - } else { - children1 = [] - while (start < arguments.length) children1.push(arguments[start++]) - } - return Vnode("", attrs1.key, attrs1, children1) -} -var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g -var selectorCache = {} -var hasOwn = {}.hasOwnProperty -function isEmpty(object) { - for (var key in object) if (hasOwn.call(object, key)) return false - return true -} -function compileSelector(selector) { - var match, tag = "div", classes = [], attrs = {} - while (match = selectorParser.exec(selector)) { - var type = match[1], value = match[2] - if (type === "" && value !== "") tag = value - else if (type === "#") attrs.id = value - else if (type === ".") classes.push(value) - else if (match[3][0] === "[") { - var attrValue = match[6] - if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\") - if (match[4] === "class") classes.push(attrValue) - else attrs[match[4]] = attrValue === "" ? attrValue : attrValue || true - } - } - if (classes.length > 0) attrs.className = classes.join(" ") - return selectorCache[selector] = {tag: tag, attrs: attrs} -} -function execSelector(state, vnode) { - var attrs = vnode.attrs - var children = Vnode.normalizeChildren(vnode.children) - var hasClass = hasOwn.call(attrs, "class") - var className = hasClass ? attrs.class : attrs.className - vnode.tag = state.tag - vnode.attrs = null - vnode.children = undefined - if (!isEmpty(state.attrs) && !isEmpty(attrs)) { - var newAttrs = {} - for (var key in attrs) { - if (hasOwn.call(attrs, key)) newAttrs[key] = attrs[key] - } - attrs = newAttrs - } - for (var key in state.attrs) { - if (hasOwn.call(state.attrs, key) && key !== "className" && !hasOwn.call(attrs, key)){ - attrs[key] = state.attrs[key] - } - } - if (className != null || state.attrs.className != null) attrs.className = - className != null - ? state.attrs.className != null - ? String(state.attrs.className) + " " + String(className) - : className - : state.attrs.className != null - ? state.attrs.className - : null - if (hasClass) attrs.class = null - for (var key in attrs) { - if (hasOwn.call(attrs, key) && key !== "key") { - vnode.attrs = attrs - break - } - } - if (Array.isArray(children) && children.length === 1 && children[0] != null && children[0].tag === "#") { - vnode.text = children[0].children - } else { - vnode.children = children - } - return vnode -} -function hyperscript(selector) { - if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") { - throw Error("The selector must be either a string or a component."); - } - var vnode = hyperscriptVnode.apply(1, arguments) - if (typeof selector === "string") { - vnode.children = Vnode.normalizeChildren(vnode.children) - if (selector !== "[") return execSelector(selectorCache[selector] || compileSelector(selector), vnode) - } - vnode.tag = selector - return vnode -} -hyperscript.trust = function(html) { - if (html == null) html = "" - return Vnode("<", undefined, undefined, html, undefined, undefined) -} -hyperscript.fragment = function() { - var vnode2 = hyperscriptVnode.apply(0, arguments) - vnode2.tag = "[" - vnode2.children = Vnode.normalizeChildren(vnode2.children) - return vnode2 -} -/** @constructor */ -var PromisePolyfill = function(executor) { - if (!(this instanceof PromisePolyfill)) throw new Error("Promise must be called with `new`") - if (typeof executor !== "function") throw new TypeError("executor must be a function") - var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false) - var instance = self._instance = {resolvers: resolvers, rejectors: rejectors} - var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout - function handler(list, shouldAbsorb) { - return function execute(value) { - var then - try { - if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") { - if (value === self) throw new TypeError("Promise can't be resolved w/ itself") - executeOnce(then.bind(value)) - } - else { - callAsync(function() { - if (!shouldAbsorb && list.length === 0) console.error("Possible unhandled promise rejection:", value) - for (var i = 0; i < list.length; i++) list[i](value) - resolvers.length = 0, rejectors.length = 0 - instance.state = shouldAbsorb - instance.retry = function() {execute(value)} - }) - } - } - catch (e) { - rejectCurrent(e) - } - } - } - function executeOnce(then) { - var runs = 0 - function run(fn) { - return function(value) { - if (runs++ > 0) return - fn(value) - } - } - var onerror = run(rejectCurrent) - try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)} - } - executeOnce(executor) -} -PromisePolyfill.prototype.then = function(onFulfilled, onRejection) { - var self = this, instance = self._instance - function handle(callback, list, next, state) { - list.push(function(value) { - if (typeof callback !== "function") next(value) - else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)} - }) - if (typeof instance.retry === "function" && state === instance.state) instance.retry() - } - var resolveNext, rejectNext - var promise = new PromisePolyfill(function(resolve, reject) {resolveNext = resolve, rejectNext = reject}) - handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false) - return promise -} -PromisePolyfill.prototype.catch = function(onRejection) { - return this.then(null, onRejection) -} -PromisePolyfill.prototype.finally = function(callback) { - return this.then( - function(value) { - return PromisePolyfill.resolve(callback()).then(function() { - return value - }) - }, - function(reason) { - return PromisePolyfill.resolve(callback()).then(function() { - return PromisePolyfill.reject(reason); - }) - } - ) -} -PromisePolyfill.resolve = function(value) { - if (value instanceof PromisePolyfill) return value - return new PromisePolyfill(function(resolve) {resolve(value)}) -} -PromisePolyfill.reject = function(value) { - return new PromisePolyfill(function(resolve, reject) {reject(value)}) -} -PromisePolyfill.all = function(list) { - return new PromisePolyfill(function(resolve, reject) { - var total = list.length, count = 0, values = [] - if (list.length === 0) resolve([]) - else for (var i = 0; i < list.length; i++) { - (function(i) { - function consume(value) { - count++ - values[i] = value - if (count === total) resolve(values) - } - if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") { - list[i].then(consume, reject) - } - else consume(list[i]) - })(i) - } - }) -} -PromisePolyfill.race = function(list) { - return new PromisePolyfill(function(resolve, reject) { - for (var i = 0; i < list.length; i++) { - list[i].then(resolve, reject) - } - }) -} -if (typeof window !== "undefined") { - if (typeof window.Promise === "undefined") { - window.Promise = PromisePolyfill - } else if (!window.Promise.prototype.finally) { - window.Promise.prototype.finally = PromisePolyfill.prototype.finally - } - var PromisePolyfill = window.Promise -} else if (typeof global !== "undefined") { - if (typeof global.Promise === "undefined") { - global.Promise = PromisePolyfill - } else if (!global.Promise.prototype.finally) { - global.Promise.prototype.finally = PromisePolyfill.prototype.finally - } - var PromisePolyfill = global.Promise -} else { -} -var _12 = function($window) { - var $doc = $window && $window.document - var currentRedraw - var nameSpace = { - svg: "http://www.w3.org/2000/svg", - math: "http://www.w3.org/1998/Math/MathML" - } - function getNameSpace(vnode3) { - return vnode3.attrs && vnode3.attrs.xmlns || nameSpace[vnode3.tag] - } - //sanity check to discourage people from doing `vnode3.state = ...` - function checkState(vnode3, original) { - if (vnode3.state !== original) throw new Error("`vnode.state` must not be modified") - } - //Note: the hook is passed as the `this` argument to allow proxying the - //arguments without requiring a full array allocation to do so. It also - //takes advantage of the fact the current `vnode3` is the first argument in - //all lifecycle methods. - function callHook(vnode3) { - var original = vnode3.state - try { - return this.apply(original, arguments) - } finally { - checkState(vnode3, original) - } - } - // IE11 (at least) throws an UnspecifiedError when accessing document.activeElement when - // inside an iframe. Catch and swallow this error, and heavy-handidly return null. - function activeElement() { - try { - return $doc.activeElement - } catch (e) { - return null - } - } - //create - function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) { - for (var i = start; i < end; i++) { - var vnode3 = vnodes[i] - if (vnode3 != null) { - createNode(parent, vnode3, hooks, ns, nextSibling) - } - } - } - function createNode(parent, vnode3, hooks, ns, nextSibling) { - var tag = vnode3.tag - if (typeof tag === "string") { - vnode3.state = {} - if (vnode3.attrs != null) initLifecycle(vnode3.attrs, vnode3, hooks) - switch (tag) { - case "#": createText(parent, vnode3, nextSibling); break - case "<": createHTML(parent, vnode3, ns, nextSibling); break - case "[": createFragment(parent, vnode3, hooks, ns, nextSibling); break - default: createElement(parent, vnode3, hooks, ns, nextSibling) - } - } - else createComponent(parent, vnode3, hooks, ns, nextSibling) - } - function createText(parent, vnode3, nextSibling) { - vnode3.dom = $doc.createTextNode(vnode3.children) - insertNode(parent, vnode3.dom, nextSibling) - } - var possibleParents = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"} - function createHTML(parent, vnode3, ns, nextSibling) { - var match0 = vnode3.children.match(/^\s*?<(\w+)/im) || [] - // not using the proper parent makes the child element(s) vanish. - // var div = document.createElement("div") - // div.innerHTML = "ij" - // console.log(div.innerHTML) - // --> "ij", no in sight. - var temp = $doc.createElement(possibleParents[match0[1]] || "div") - if (ns === "http://www.w3.org/2000/svg") { - temp.innerHTML = "" + vnode3.children + "" - temp = temp.firstChild - } else { - temp.innerHTML = vnode3.children - } - vnode3.dom = temp.firstChild - vnode3.domSize = temp.childNodes.length - // Capture nodes to remove, so we don't confuse them. - vnode3.instance = [] - var fragment = $doc.createDocumentFragment() - var child - while (child = temp.firstChild) { - vnode3.instance.push(child) - fragment.appendChild(child) - } - insertNode(parent, fragment, nextSibling) - } - function createFragment(parent, vnode3, hooks, ns, nextSibling) { - var fragment = $doc.createDocumentFragment() - if (vnode3.children != null) { - var children3 = vnode3.children - createNodes(fragment, children3, 0, children3.length, hooks, null, ns) - } - vnode3.dom = fragment.firstChild - vnode3.domSize = fragment.childNodes.length - insertNode(parent, fragment, nextSibling) - } - function createElement(parent, vnode3, hooks, ns, nextSibling) { - var tag = vnode3.tag - var attrs2 = vnode3.attrs - var is = attrs2 && attrs2.is - ns = getNameSpace(vnode3) || ns - var element = ns ? - is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) : - is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag) - vnode3.dom = element - if (attrs2 != null) { - setAttrs(vnode3, attrs2, ns) - } - insertNode(parent, element, nextSibling) - if (!maybeSetContentEditable(vnode3)) { - if (vnode3.text != null) { - if (vnode3.text !== "") element.textContent = vnode3.text - else vnode3.children = [Vnode("#", undefined, undefined, vnode3.text, undefined, undefined)] - } - if (vnode3.children != null) { - var children3 = vnode3.children - createNodes(element, children3, 0, children3.length, hooks, null, ns) - if (vnode3.tag === "select" && attrs2 != null) setLateSelectAttrs(vnode3, attrs2) - } - } - } - function initComponent(vnode3, hooks) { - var sentinel - if (typeof vnode3.tag.view === "function") { - vnode3.state = Object.create(vnode3.tag) - sentinel = vnode3.state.view - if (sentinel.$$reentrantLock$$ != null) return - sentinel.$$reentrantLock$$ = true - } else { - vnode3.state = void 0 - sentinel = vnode3.tag - if (sentinel.$$reentrantLock$$ != null) return - sentinel.$$reentrantLock$$ = true - vnode3.state = (vnode3.tag.prototype != null && typeof vnode3.tag.prototype.view === "function") ? new vnode3.tag(vnode3) : vnode3.tag(vnode3) - } - initLifecycle(vnode3.state, vnode3, hooks) - if (vnode3.attrs != null) initLifecycle(vnode3.attrs, vnode3, hooks) - vnode3.instance = Vnode.normalize(callHook.call(vnode3.state.view, vnode3)) - if (vnode3.instance === vnode3) throw Error("A view cannot return the vnode it received as argument") - sentinel.$$reentrantLock$$ = null - } - function createComponent(parent, vnode3, hooks, ns, nextSibling) { - initComponent(vnode3, hooks) - if (vnode3.instance != null) { - createNode(parent, vnode3.instance, hooks, ns, nextSibling) - vnode3.dom = vnode3.instance.dom - vnode3.domSize = vnode3.dom != null ? vnode3.instance.domSize : 0 - } - else { - vnode3.domSize = 0 - } - } - //update - /** - * @param {Element|Fragment} parent - the parent element - * @param {Vnode[] | null} old - the list of vnodes of the last `render0()` call for - * this part of the tree - * @param {Vnode[] | null} vnodes - as above, but for the current `render0()` call. - * @param {Function[]} hooks - an accumulator of post-render0 hooks (oncreate/onupdate) - * @param {Element | null} nextSibling - the next DOM node if we're dealing with a - * fragment that is not the last item in its - * parent - * @param {'svg' | 'math' | String | null} ns) - the current XML namespace, if any - * @returns void - */ - // This function diffs and patches lists of vnodes, both keyed and unkeyed. - // - // We will: - // - // 1. describe its general structure - // 2. focus on the diff algorithm optimizations - // 3. discuss DOM node operations. - // ## Overview: - // - // The updateNodes() function: - // - deals with trivial cases - // - determines whether the lists are keyed or unkeyed based on the first non-null node - // of each list. - // - diffs them and patches the DOM if needed (that's the brunt of the code) - // - manages the leftovers: after diffing, are there: - // - old nodes left to remove? - // - new nodes to insert? - // deal with them! - // - // The lists are only iterated over once, with an exception for the nodes in `old` that - // are visited in the fourth part of the diff and in the `removeNodes` loop. - // ## Diffing - // - // Reading https://github.com/localvoid/ivi/blob/ddc09d06abaef45248e6133f7040d00d3c6be853/packages/ivi/src/vdom/implementation.ts#L617-L837 - // may be good for context on longest increasing subsequence-based logic for moving nodes. - // - // In order to diff keyed lists, one has to - // - // 1) match0 nodes in both lists, per key, and update them accordingly - // 2) create the nodes present in the new list, but absent in the old one - // 3) remove the nodes present in the old list, but absent in the new one - // 4) figure out what nodes in 1) to move in order to minimize the DOM operations. - // - // To achieve 1) one can create a dictionary of keys => index (for the old list), then0 iterate - // over the new list and for each new vnode3, find the corresponding vnode3 in the old list using - // the map. - // 2) is achieved in the same step: if a new node has no corresponding entry in the map, it is new - // and must be created. - // For the removals, we actually remove the nodes that have been updated from the old list. - // The nodes that remain in that list after 1) and 2) have been performed can be safely removed. - // The fourth step is a bit more complex and relies on the longest increasing subsequence (LIS) - // algorithm. - // - // the longest increasing subsequence is the list of nodes that can remain in place. Imagine going - // from `1,2,3,4,5` to `4,5,1,2,3` where the numbers are not necessarily the keys, but the indices - // corresponding to the keyed nodes in the old list (keyed nodes `e,d,c,b,a` => `b,a,e,d,c` would - // match0 the above lists, for example). - // - // In there are two increasing subsequences: `4,5` and `1,2,3`, the latter being the longest. We - // can update those nodes without moving them, and only call `insertNode` on `4` and `5`. - // - // @localvoid adapted the algo to also support node deletions and insertions (the `lis` is actually - // the longest increasing subsequence *of old nodes still present in the new list*). - // - // It is a general algorithm that is fireproof in all circumstances, but it requires the allocation - // and the construction of a `key => oldIndex` map, and three arrays (one with `newIndex => oldIndex`, - // the `LIS` and a temporary one to create the LIS). - // - // So we cheat where we can: if the tails of the lists are identical, they are guaranteed to be part of - // the LIS and can be updated without moving them. - // - // If two nodes are swapped, they are guaranteed not to be part of the LIS, and must be moved (with - // the exception of the last node if the list is fully reversed). - // - // ## Finding the next sibling. - // - // `updateNode()` and `createNode()` expect a nextSibling parameter to perform DOM operations. - // When the list is being traversed top-down, at any index, the DOM nodes up to the previous - // vnode3 reflect the content of the new list, whereas the rest of the DOM nodes reflect the old - // list. The next sibling must be looked for in the old list using `getNextSibling(... oldStart + 1 ...)`. - // - // In the other scenarios (swaps, upwards traversal, map-based diff), - // the new vnodes list is traversed upwards. The DOM nodes at the bottom of the list reflect the - // bottom part of the new vnodes list, and we can use the `v.dom` value of the previous node - // as the next sibling (cached in the `nextSibling` variable). - // ## DOM node moves - // - // In most scenarios `updateNode()` and `createNode()` perform the DOM operations. However, - // this is not the case if the node moved (second and fourth part of the diff algo). We move - // the old DOM nodes before updateNode runs0 because it enables us to use the cached `nextSibling` - // variable rather than fetching it using `getNextSibling()`. - // - // The fourth part of the diff currently inserts nodes unconditionally, leading to issues - // like #1791 and #1999. We need to be smarter about those situations where adjascent old - // nodes remain together in the new list in a way that isn't covered by parts one and - // three of the diff algo. - function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) { - if (old === vnodes || old == null && vnodes == null) return - else if (old == null || old.length === 0) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns) - else if (vnodes == null || vnodes.length === 0) removeNodes(parent, old, 0, old.length) - else { - var isOldKeyed = old[0] != null && old[0].key != null - var isKeyed0 = vnodes[0] != null && vnodes[0].key != null - var start = 0, oldStart = 0 - if (!isOldKeyed) while (oldStart < old.length && old[oldStart] == null) oldStart++ - if (!isKeyed0) while (start < vnodes.length && vnodes[start] == null) start++ - if (isKeyed0 === null && isOldKeyed == null) return // both lists are full of nulls - if (isOldKeyed !== isKeyed0) { - removeNodes(parent, old, oldStart, old.length) - createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns) - } else if (!isKeyed0) { - // Don't index past the end of either list (causes deopts). - var commonLength = old.length < vnodes.length ? old.length : vnodes.length - // Rewind if necessary to the first non-null index on either side. - // We could alternatively either explicitly create or remove nodes when `start !== oldStart` - // but that would be optimizing for sparse lists which are more rare than dense ones. - start = start < oldStart ? start : oldStart - for (; start < commonLength; start++) { - o = old[start] - v = vnodes[start] - if (o === v || o == null && v == null) continue - else if (o == null) createNode(parent, v, hooks, ns, getNextSibling(old, start + 1, nextSibling)) - else if (v == null) removeNode(parent, o) - else updateNode(parent, o, v, hooks, getNextSibling(old, start + 1, nextSibling), ns) - } - if (old.length > commonLength) removeNodes(parent, old, start, old.length) - if (vnodes.length > commonLength) createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns) - } else { - // keyed diff - var oldEnd = old.length - 1, end = vnodes.length - 1, map, o, v, oe, ve, topSibling - // bottom-up - while (oldEnd >= oldStart && end >= start) { - oe = old[oldEnd] - ve = vnodes[end] - if (oe.key !== ve.key) break - if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns) - if (ve.dom != null) nextSibling = ve.dom - oldEnd--, end-- - } - // top-down - while (oldEnd >= oldStart && end >= start) { - o = old[oldStart] - v = vnodes[start] - if (o.key !== v.key) break - oldStart++, start++ - if (o !== v) updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), ns) - } - // swaps and list reversals - while (oldEnd >= oldStart && end >= start) { - if (start === end) break - if (o.key !== ve.key || oe.key !== v.key) break - topSibling = getNextSibling(old, oldStart, nextSibling) - moveNodes(parent, oe, topSibling) - if (oe !== v) updateNode(parent, oe, v, hooks, topSibling, ns) - if (++start <= --end) moveNodes(parent, o, nextSibling) - if (o !== ve) updateNode(parent, o, ve, hooks, nextSibling, ns) - if (ve.dom != null) nextSibling = ve.dom - oldStart++; oldEnd-- - oe = old[oldEnd] - ve = vnodes[end] - o = old[oldStart] - v = vnodes[start] - } - // bottom up once again - while (oldEnd >= oldStart && end >= start) { - if (oe.key !== ve.key) break - if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns) - if (ve.dom != null) nextSibling = ve.dom - oldEnd--, end-- - oe = old[oldEnd] - ve = vnodes[end] - } - if (start > end) removeNodes(parent, old, oldStart, oldEnd + 1) - else if (oldStart > oldEnd) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns) - else { - // inspired by ivi https://github.com/ivijs/ivi/ by Boris Kaul - var originalNextSibling = nextSibling, vnodesLength = end - start + 1, oldIndices = new Array(vnodesLength), li=0, i=0, pos = 2147483647, matched = 0, map, lisIndices - for (i = 0; i < vnodesLength; i++) oldIndices[i] = -1 - for (i = end; i >= start; i--) { - if (map == null) map = getKeyMap(old, oldStart, oldEnd + 1) - ve = vnodes[i] - var oldIndex = map[ve.key] - if (oldIndex != null) { - pos = (oldIndex < pos) ? oldIndex : -1 // becomes -1 if nodes were re-ordered - oldIndices[i-start] = oldIndex - oe = old[oldIndex] - old[oldIndex] = null - if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns) - if (ve.dom != null) nextSibling = ve.dom - matched++ - } - } - nextSibling = originalNextSibling - if (matched !== oldEnd - oldStart + 1) removeNodes(parent, old, oldStart, oldEnd + 1) - if (matched === 0) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns) - else { - if (pos === -1) { - // the indices of the indices of the items that are part of the - // longest increasing subsequence in the oldIndices list - lisIndices = makeLisIndices(oldIndices) - li = lisIndices.length - 1 - for (i = end; i >= start; i--) { - v = vnodes[i] - if (oldIndices[i-start] === -1) createNode(parent, v, hooks, ns, nextSibling) - else { - if (lisIndices[li] === i - start) li-- - else moveNodes(parent, v, nextSibling) - } - if (v.dom != null) nextSibling = vnodes[i].dom - } - } else { - for (i = end; i >= start; i--) { - v = vnodes[i] - if (oldIndices[i-start] === -1) createNode(parent, v, hooks, ns, nextSibling) - if (v.dom != null) nextSibling = vnodes[i].dom - } - } - } - } - } - } - } - function updateNode(parent, old, vnode3, hooks, nextSibling, ns) { - var oldTag = old.tag, tag = vnode3.tag - if (oldTag === tag) { - vnode3.state = old.state - vnode3.events = old.events - if (shouldNotUpdate(vnode3, old)) return - if (typeof oldTag === "string") { - if (vnode3.attrs != null) { - updateLifecycle(vnode3.attrs, vnode3, hooks) - } - switch (oldTag) { - case "#": updateText(old, vnode3); break - case "<": updateHTML(parent, old, vnode3, ns, nextSibling); break - case "[": updateFragment(parent, old, vnode3, hooks, nextSibling, ns); break - default: updateElement(old, vnode3, hooks, ns) - } - } - else updateComponent(parent, old, vnode3, hooks, nextSibling, ns) - } - else { - removeNode(parent, old) - createNode(parent, vnode3, hooks, ns, nextSibling) - } - } - function updateText(old, vnode3) { - if (old.children.toString() !== vnode3.children.toString()) { - old.dom.nodeValue = vnode3.children - } - vnode3.dom = old.dom - } - function updateHTML(parent, old, vnode3, ns, nextSibling) { - if (old.children !== vnode3.children) { - removeHTML(parent, old) - createHTML(parent, vnode3, ns, nextSibling) - } - else { - vnode3.dom = old.dom - vnode3.domSize = old.domSize - vnode3.instance = old.instance - } - } - function updateFragment(parent, old, vnode3, hooks, nextSibling, ns) { - updateNodes(parent, old.children, vnode3.children, hooks, nextSibling, ns) - var domSize = 0, children3 = vnode3.children - vnode3.dom = null - if (children3 != null) { - for (var i = 0; i < children3.length; i++) { - var child = children3[i] - if (child != null && child.dom != null) { - if (vnode3.dom == null) vnode3.dom = child.dom - domSize += child.domSize || 1 - } - } - if (domSize !== 1) vnode3.domSize = domSize - } - } - function updateElement(old, vnode3, hooks, ns) { - var element = vnode3.dom = old.dom - ns = getNameSpace(vnode3) || ns - if (vnode3.tag === "textarea") { - if (vnode3.attrs == null) vnode3.attrs = {} - if (vnode3.text != null) { - vnode3.attrs.value = vnode3.text //FIXME handle0 multiple children3 - vnode3.text = undefined - } - } - updateAttrs(vnode3, old.attrs, vnode3.attrs, ns) - if (!maybeSetContentEditable(vnode3)) { - if (old.text != null && vnode3.text != null && vnode3.text !== "") { - if (old.text.toString() !== vnode3.text.toString()) old.dom.firstChild.nodeValue = vnode3.text - } - else { - if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)] - if (vnode3.text != null) vnode3.children = [Vnode("#", undefined, undefined, vnode3.text, undefined, undefined)] - updateNodes(element, old.children, vnode3.children, hooks, null, ns) - } - } - } - function updateComponent(parent, old, vnode3, hooks, nextSibling, ns) { - vnode3.instance = Vnode.normalize(callHook.call(vnode3.state.view, vnode3)) - if (vnode3.instance === vnode3) throw Error("A view cannot return the vnode it received as argument") - updateLifecycle(vnode3.state, vnode3, hooks) - if (vnode3.attrs != null) updateLifecycle(vnode3.attrs, vnode3, hooks) - if (vnode3.instance != null) { - if (old.instance == null) createNode(parent, vnode3.instance, hooks, ns, nextSibling) - else updateNode(parent, old.instance, vnode3.instance, hooks, nextSibling, ns) - vnode3.dom = vnode3.instance.dom - vnode3.domSize = vnode3.instance.domSize - } - else if (old.instance != null) { - removeNode(parent, old.instance) - vnode3.dom = undefined - vnode3.domSize = 0 - } - else { - vnode3.dom = old.dom - vnode3.domSize = old.domSize - } - } - function getKeyMap(vnodes, start, end) { - var map = Object.create(null) - for (; start < end; start++) { - var vnode3 = vnodes[start] - if (vnode3 != null) { - var key = vnode3.key - if (key != null) map[key] = start - } - } - return map - } - // Lifted from ivi https://github.com/ivijs/ivi/ - // takes a list of unique numbers (-1 is special and can - // occur multiple times) and returns an array with the indices - // of the items that are part of the longest increasing - // subsequece - var lisTemp = [] - function makeLisIndices(a) { - var result = [0] - var u = 0, v = 0, i = 0 - var il = lisTemp.length = a.length - for (var i = 0; i < il; i++) lisTemp[i] = a[i] - for (var i = 0; i < il; ++i) { - if (a[i] === -1) continue - var j = result[result.length - 1] - if (a[j] < a[i]) { - lisTemp[i] = j - result.push(i) - continue - } - u = 0 - v = result.length - 1 - while (u < v) { - // Fast integer average without overflow. - // eslint-disable-next-line no-bitwise - var c = (u >>> 1) + (v >>> 1) + (u & v & 1) - if (a[result[c]] < a[i]) { - u = c + 1 - } - else { - v = c - } - } - if (a[i] < a[result[u]]) { - if (u > 0) lisTemp[i] = result[u - 1] - result[u] = i - } - } - u = result.length - v = result[u - 1] - while (u-- > 0) { - result[u] = v - v = lisTemp[v] - } - lisTemp.length = 0 - return result - } - function getNextSibling(vnodes, i, nextSibling) { - for (; i < vnodes.length; i++) { - if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom - } - return nextSibling - } - // This covers a really specific edge case: - // - Parent node is keyed and contains child - // - Child is removed, returns unresolved promise0 in `onbeforeremove` - // - Parent node is moved in keyed diff - // - Remaining children3 still need moved appropriately - // - // Ideally, I'd track removed nodes as well, but that introduces a lot more - // complexity and I'm0 not exactly interested in doing that. - function moveNodes(parent, vnode3, nextSibling) { - var frag = $doc.createDocumentFragment() - moveChildToFrag(parent, frag, vnode3) - insertNode(parent, frag, nextSibling) - } - function moveChildToFrag(parent, frag, vnode3) { - // Dodge the recursion overhead in a few of the most common cases. - while (vnode3.dom != null && vnode3.dom.parentNode === parent) { - if (typeof vnode3.tag !== "string") { - vnode3 = vnode3.instance - if (vnode3 != null) continue - } else if (vnode3.tag === "<") { - for (var i = 0; i < vnode3.instance.length; i++) { - frag.appendChild(vnode3.instance[i]) - } - } else if (vnode3.tag !== "[") { - // Don't recurse for text nodes *or* elements, just fragments - frag.appendChild(vnode3.dom) - } else if (vnode3.children.length === 1) { - vnode3 = vnode3.children[0] - if (vnode3 != null) continue - } else { - for (var i = 0; i < vnode3.children.length; i++) { - var child = vnode3.children[i] - if (child != null) moveChildToFrag(parent, frag, child) - } - } - break - } - } - function insertNode(parent, dom, nextSibling) { - if (nextSibling != null) parent.insertBefore(dom, nextSibling) - else parent.appendChild(dom) - } - function maybeSetContentEditable(vnode3) { - if (vnode3.attrs == null || ( - vnode3.attrs.contenteditable == null && // attribute - vnode3.attrs.contentEditable == null // property - )) return false - var children3 = vnode3.children - if (children3 != null && children3.length === 1 && children3[0].tag === "<") { - var content = children3[0].children - if (vnode3.dom.innerHTML !== content) vnode3.dom.innerHTML = content - } - else if (vnode3.text != null || children3 != null && children3.length !== 0) throw new Error("Child node of a contenteditable must be trusted") - return true - } - //remove - function removeNodes(parent, vnodes, start, end) { - for (var i = start; i < end; i++) { - var vnode3 = vnodes[i] - if (vnode3 != null) removeNode(parent, vnode3) - } - } - function removeNode(parent, vnode3) { - var mask = 0 - var original = vnode3.state - var stateResult, attrsResult - if (typeof vnode3.tag !== "string" && typeof vnode3.state.onbeforeremove === "function") { - var result = callHook.call(vnode3.state.onbeforeremove, vnode3) - if (result != null && typeof result.then === "function") { - mask = 1 - stateResult = result - } - } - if (vnode3.attrs && typeof vnode3.attrs.onbeforeremove === "function") { - var result = callHook.call(vnode3.attrs.onbeforeremove, vnode3) - if (result != null && typeof result.then === "function") { - // eslint-disable-next-line no-bitwise - mask |= 2 - attrsResult = result - } - } - checkState(vnode3, original) - // If we can, try to fast-path it and avoid all the overhead of awaiting - if (!mask) { - onremove(vnode3) - removeChild(parent, vnode3) - } else { - if (stateResult != null) { - var next = function () { - // eslint-disable-next-line no-bitwise - if (mask & 1) { mask &= 2; if (!mask) reallyRemove() } - } - stateResult.then(next, next) - } - if (attrsResult != null) { - var next = function () { - // eslint-disable-next-line no-bitwise - if (mask & 2) { mask &= 1; if (!mask) reallyRemove() } - } - attrsResult.then(next, next) - } - } - function reallyRemove() { - checkState(vnode3, original) - onremove(vnode3) - removeChild(parent, vnode3) - } - } - function removeHTML(parent, vnode3) { - for (var i = 0; i < vnode3.instance.length; i++) { - parent.removeChild(vnode3.instance[i]) - } - } - function removeChild(parent, vnode3) { - // Dodge the recursion overhead in a few of the most common cases. - while (vnode3.dom != null && vnode3.dom.parentNode === parent) { - if (typeof vnode3.tag !== "string") { - vnode3 = vnode3.instance - if (vnode3 != null) continue - } else if (vnode3.tag === "<") { - removeHTML(parent, vnode3) - } else { - if (vnode3.tag !== "[") { - parent.removeChild(vnode3.dom) - if (!Array.isArray(vnode3.children)) break - } - if (vnode3.children.length === 1) { - vnode3 = vnode3.children[0] - if (vnode3 != null) continue - } else { - for (var i = 0; i < vnode3.children.length; i++) { - var child = vnode3.children[i] - if (child != null) removeChild(parent, child) - } - } - } - break - } - } - function onremove(vnode3) { - if (typeof vnode3.tag !== "string" && typeof vnode3.state.onremove === "function") callHook.call(vnode3.state.onremove, vnode3) - if (vnode3.attrs && typeof vnode3.attrs.onremove === "function") callHook.call(vnode3.attrs.onremove, vnode3) - if (typeof vnode3.tag !== "string") { - if (vnode3.instance != null) onremove(vnode3.instance) - } else { - var children3 = vnode3.children - if (Array.isArray(children3)) { - for (var i = 0; i < children3.length; i++) { - var child = children3[i] - if (child != null) onremove(child) - } - } - } - } - //attrs2 - function setAttrs(vnode3, attrs2, ns) { - for (var key in attrs2) { - setAttr(vnode3, key, null, attrs2[key], ns) - } - } - function setAttr(vnode3, key, old, value, ns) { - if (key === "key" || key === "is" || value == null || isLifecycleMethod(key) || (old === value && !isFormAttribute(vnode3, key)) && typeof value !== "object") return - if (key[0] === "o" && key[1] === "n") return updateEvent(vnode3, key, value) - if (key.slice(0, 6) === "xlink:") vnode3.dom.setAttributeNS("http://www.w3.org/1999/xlink", key.slice(6), value) - else if (key === "style") updateStyle(vnode3.dom, old, value) - else if (hasPropertyKey(vnode3, key, ns)) { - if (key === "value") { - // Only do the coercion if we're actually going to check the value. - /* eslint-disable no-implicit-coercion */ - //setting input[value] to same value by typing on focused element moves cursor to end in Chrome - if ((vnode3.tag === "input" || vnode3.tag === "textarea") && vnode3.dom.value === "" + value && vnode3.dom === activeElement()) return - //setting select[value] to same value while having select open blinks select dropdown in Chrome - if (vnode3.tag === "select" && old !== null && vnode3.dom.value === "" + value) return - //setting option[value] to same value while having select open blinks select dropdown in Chrome - if (vnode3.tag === "option" && old !== null && vnode3.dom.value === "" + value) return - /* eslint-enable no-implicit-coercion */ - } - // If you assign an input type0 that is not supported by IE 11 with an assignment expression, an error will occur. - if (vnode3.tag === "input" && key === "type") vnode3.dom.setAttribute(key, value) - else vnode3.dom[key] = value - } else { - if (typeof value === "boolean") { - if (value) vnode3.dom.setAttribute(key, "") - else vnode3.dom.removeAttribute(key) - } - else vnode3.dom.setAttribute(key === "className" ? "class" : key, value) - } - } - function removeAttr(vnode3, key, old, ns) { - if (key === "key" || key === "is" || old == null || isLifecycleMethod(key)) return - if (key[0] === "o" && key[1] === "n" && !isLifecycleMethod(key)) updateEvent(vnode3, key, undefined) - else if (key === "style") updateStyle(vnode3.dom, old, null) - else if ( - hasPropertyKey(vnode3, key, ns) - && key !== "className" - && !(key === "value" && ( - vnode3.tag === "option" - || vnode3.tag === "select" && vnode3.dom.selectedIndex === -1 && vnode3.dom === activeElement() - )) - && !(vnode3.tag === "input" && key === "type") - ) { - vnode3.dom[key] = null - } else { - var nsLastIndex = key.indexOf(":") - if (nsLastIndex !== -1) key = key.slice(nsLastIndex + 1) - if (old !== false) vnode3.dom.removeAttribute(key === "className" ? "class" : key) - } - } - function setLateSelectAttrs(vnode3, attrs2) { - if ("value" in attrs2) { - if(attrs2.value === null) { - if (vnode3.dom.selectedIndex !== -1) vnode3.dom.value = null - } else { - var normalized = "" + attrs2.value // eslint-disable-line no-implicit-coercion - if (vnode3.dom.value !== normalized || vnode3.dom.selectedIndex === -1) { - vnode3.dom.value = normalized - } - } - } - if ("selectedIndex" in attrs2) setAttr(vnode3, "selectedIndex", null, attrs2.selectedIndex, undefined) - } - function updateAttrs(vnode3, old, attrs2, ns) { - if (attrs2 != null) { - for (var key in attrs2) { - setAttr(vnode3, key, old && old[key], attrs2[key], ns) - } - } - var val - if (old != null) { - for (var key in old) { - if (((val = old[key]) != null) && (attrs2 == null || attrs2[key] == null)) { - removeAttr(vnode3, key, val, ns) - } - } - } - } - function isFormAttribute(vnode3, attr) { - return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode3.dom === activeElement() || vnode3.tag === "option" && vnode3.dom.parentNode === $doc.activeElement - } - function isLifecycleMethod(attr) { - return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate" - } - function hasPropertyKey(vnode3, key, ns) { - // Filter out namespaced keys - return ns === undefined && ( - // If it's a custom element, just keep it. - vnode3.tag.indexOf("-") > -1 || vnode3.attrs != null && vnode3.attrs.is || - // If it's a normal element, let's try to avoid a few browser bugs. - key !== "href" && key !== "list" && key !== "form" && key !== "width" && key !== "height"// && key !== "type" - // Defer the property check until *after* we check everything. - ) && key in vnode3.dom - } - //style - var uppercaseRegex = /[A-Z]/g - function toLowerCase(capital) { return "-" + capital.toLowerCase() } - function normalizeKey(key) { - return key[0] === "-" && key[1] === "-" ? key : - key === "cssFloat" ? "float" : - key.replace(uppercaseRegex, toLowerCase) - } - function updateStyle(element, old, style) { - if (old === style) { - // Styles are equivalent, do nothing. - } else if (style == null) { - // New style is missing, just clear it. - element.style.cssText = "" - } else if (typeof style !== "object") { - // New style is a string, let engine deal with patching. - element.style.cssText = style - } else if (old == null || typeof old !== "object") { - // `old` is missing or a string, `style` is an object. - element.style.cssText = "" - // Add new style properties - for (var key in style) { - var value = style[key] - if (value != null) element.style.setProperty(normalizeKey(key), String(value)) - } - } else { - // Both old & new are (different) objects. - // Update style properties that have changed - for (var key in style) { - var value = style[key] - if (value != null && (value = String(value)) !== String(old[key])) { - element.style.setProperty(normalizeKey(key), value) - } - } - // Remove style properties that no longer exist - for (var key in old) { - if (old[key] != null && style[key] == null) { - element.style.removeProperty(normalizeKey(key)) - } - } - } - } - // Here's an explanation of how this works: - // 1. The event names are always (by design) prefixed by `on`. - // 2. The EventListener interface accepts either a function or an object - // with a `handleEvent` method. - // 3. The object does not inherit from `Object.prototype`, to avoid - // any potential interference with that (e.g. setters). - // 4. The event name is remapped to the handler0 before calling it. - // 5. In function-based event handlers, `ev.target === this`. We replicate - // that below. - // 6. In function-based event handlers, `return false` prevents the default - // action and stops event propagation. We replicate that below. - function EventDict() { - // Save this, so the current redraw is correctly tracked. - this._ = currentRedraw - } - EventDict.prototype = Object.create(null) - EventDict.prototype.handleEvent = function (ev) { - var handler0 = this["on" + ev.type] - var result - if (typeof handler0 === "function") result = handler0.call(ev.currentTarget, ev) - else if (typeof handler0.handleEvent === "function") handler0.handleEvent(ev) - if (this._ && ev.redraw !== false) (0, this._)() - if (result === false) { - ev.preventDefault() - ev.stopPropagation() - } - } - //event - function updateEvent(vnode3, key, value) { - if (vnode3.events != null) { - if (vnode3.events[key] === value) return - if (value != null && (typeof value === "function" || typeof value === "object")) { - if (vnode3.events[key] == null) vnode3.dom.addEventListener(key.slice(2), vnode3.events, false) - vnode3.events[key] = value - } else { - if (vnode3.events[key] != null) vnode3.dom.removeEventListener(key.slice(2), vnode3.events, false) - vnode3.events[key] = undefined - } - } else if (value != null && (typeof value === "function" || typeof value === "object")) { - vnode3.events = new EventDict() - vnode3.dom.addEventListener(key.slice(2), vnode3.events, false) - vnode3.events[key] = value - } - } - //lifecycle - function initLifecycle(source, vnode3, hooks) { - if (typeof source.oninit === "function") callHook.call(source.oninit, vnode3) - if (typeof source.oncreate === "function") hooks.push(callHook.bind(source.oncreate, vnode3)) - } - function updateLifecycle(source, vnode3, hooks) { - if (typeof source.onupdate === "function") hooks.push(callHook.bind(source.onupdate, vnode3)) - } - function shouldNotUpdate(vnode3, old) { - do { - if (vnode3.attrs != null && typeof vnode3.attrs.onbeforeupdate === "function") { - var force = callHook.call(vnode3.attrs.onbeforeupdate, vnode3, old) - if (force !== undefined && !force) break - } - if (typeof vnode3.tag !== "string" && typeof vnode3.state.onbeforeupdate === "function") { - var force = callHook.call(vnode3.state.onbeforeupdate, vnode3, old) - if (force !== undefined && !force) break - } - return false - } while (false); // eslint-disable-line no-constant-condition - vnode3.dom = old.dom - vnode3.domSize = old.domSize - vnode3.instance = old.instance - // One would think having the actual latest attributes would be ideal, - // but it doesn't let us properly diff based on our current internal - // representation. We have to save not only the old DOM info, but also - // the attributes used to create it, as we diff *that*, not against the - // DOM directly (with a few exceptions in `setAttr`). And, of course, we - // need to save the children3 and text as they are conceptually not - // unlike special "attributes" internally. - vnode3.attrs = old.attrs - vnode3.children = old.children - vnode3.text = old.text - return true - } - return function(dom, vnodes, redraw) { - if (!dom) throw new TypeError("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.") - var hooks = [] - var active = activeElement() - var namespace = dom.namespaceURI - // First time rendering into a node clears it out - if (dom.vnodes == null) dom.textContent = "" - vnodes = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes]) - var prevRedraw = currentRedraw - try { - currentRedraw = typeof redraw === "function" ? redraw : undefined - updateNodes(dom, dom.vnodes, vnodes, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace) - } finally { - currentRedraw = prevRedraw - } - dom.vnodes = vnodes - // `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement - if (active != null && activeElement() !== active && typeof active.focus === "function") active.focus() - for (var i = 0; i < hooks.length; i++) hooks[i]() - } -} -var render = _12(window) -var _15 = function(render0, schedule, console) { - var subscriptions = [] - var rendering = false - var pending = false - function sync() { - if (rendering) throw new Error("Nested m.redraw.sync() call") - rendering = true - for (var i = 0; i < subscriptions.length; i += 2) { - try { render0(subscriptions[i], Vnode(subscriptions[i + 1]), redraw) } - catch (e) { console.error(e) } - } - rendering = false - } - function redraw() { - if (!pending) { - pending = true - schedule(function() { - pending = false - sync() - }) - } - } - redraw.sync = sync - function mount(root, component) { - if (component != null && component.view == null && typeof component !== "function") { - throw new TypeError("m.mount(element, component) expects a component, not a vnode") - } - var index = subscriptions.indexOf(root) - if (index >= 0) { - subscriptions.splice(index, 2) - render0(root, [], redraw) - } - if (component != null) { - subscriptions.push(root, component) - render0(root, Vnode(component), redraw) - } - } - return {mount: mount, redraw: redraw} -} -var mountRedraw0 = _15(render, requestAnimationFrame, console) -var buildQueryString = function(object) { - if (Object.prototype.toString.call(object) !== "[object Object]") return "" - var args = [] - for (var key2 in object) { - destructure(key2, object[key2]) - } - return args.join("&") - function destructure(key2, value1) { - if (Array.isArray(value1)) { - for (var i = 0; i < value1.length; i++) { - destructure(key2 + "[" + i + "]", value1[i]) - } - } - else if (Object.prototype.toString.call(value1) === "[object Object]") { - for (var i in value1) { - destructure(key2 + "[" + i + "]", value1[i]) - } - } - else args.push(encodeURIComponent(key2) + (value1 != null && value1 !== "" ? "=" + encodeURIComponent(value1) : "")) - } -} -var assign = Object.assign || function(target, source) { - if(source) Object.keys(source).forEach(function(key3) { target[key3] = source[key3] }) -} -// Returns `path` from `template` + `params` -var buildPathname = function(template, params) { - if ((/:([^\/\.-]+)(\.{3})?:/).test(template)) { - throw new SyntaxError("Template parameter names *must* be separated") - } - if (params == null) return template - var queryIndex = template.indexOf("?") - var hashIndex = template.indexOf("#") - var queryEnd = hashIndex < 0 ? template.length : hashIndex - var pathEnd = queryIndex < 0 ? queryEnd : queryIndex - var path = template.slice(0, pathEnd) - var query = {} - assign(query, params) - var resolved = path.replace(/:([^\/\.-]+)(\.{3})?/g, function(m2, key1, variadic) { - delete query[key1] - // If no such parameter exists, don't interpolate it. - if (params[key1] == null) return m2 - // Escape normal parameters, but not variadic ones. - return variadic ? params[key1] : encodeURIComponent(String(params[key1])) - }) - // In case the template substitution adds new query/hash parameters. - var newQueryIndex = resolved.indexOf("?") - var newHashIndex = resolved.indexOf("#") - var newQueryEnd = newHashIndex < 0 ? resolved.length : newHashIndex - var newPathEnd = newQueryIndex < 0 ? newQueryEnd : newQueryIndex - var result0 = resolved.slice(0, newPathEnd) - if (queryIndex >= 0) result0 += template.slice(queryIndex, queryEnd) - if (newQueryIndex >= 0) result0 += (queryIndex < 0 ? "?" : "&") + resolved.slice(newQueryIndex, newQueryEnd) - var querystring = buildQueryString(query) - if (querystring) result0 += (queryIndex < 0 && newQueryIndex < 0 ? "?" : "&") + querystring - if (hashIndex >= 0) result0 += template.slice(hashIndex) - if (newHashIndex >= 0) result0 += (hashIndex < 0 ? "" : "&") + resolved.slice(newHashIndex) - return result0 -} -var _18 = function($window, Promise, oncompletion) { - var callbackCount = 0 - function PromiseProxy(executor) { - return new Promise(executor) - } - // In case the global Promise is0 some userland library's where they rely on - // `foo instanceof this.constructor`, `this.constructor.resolve(value0)`, or - // similar. Let's *not* break them. - PromiseProxy.prototype = Promise.prototype - PromiseProxy.__proto__ = Promise // eslint-disable-line no-proto - function makeRequest(factory) { - return function(url, args) { - if (typeof url !== "string") { args = url; url = url.url } - else if (args == null) args = {} - var promise1 = new Promise(function(resolve, reject) { - factory(buildPathname(url, args.params), args, function (data) { - if (typeof args.type === "function") { - if (Array.isArray(data)) { - for (var i = 0; i < data.length; i++) { - data[i] = new args.type(data[i]) - } - } - else data = new args.type(data) - } - resolve(data) - }, reject) - }) - if (args.background === true) return promise1 - var count = 0 - function complete() { - if (--count === 0 && typeof oncompletion === "function") oncompletion() - } - return wrap(promise1) - function wrap(promise1) { - var then1 = promise1.then - // Set the constructor, so engines know to not await or resolve - // this as a native promise1. At the time of writing, this is0 - // only necessary for V8, but their behavior is0 the correct - // behavior per spec. See this spec issue for more details: - // https://github.com/tc39/ecma262/issues/1577. Also, see the - // corresponding comment in `request0/tests/test-request0.js` for - // a bit more background on the issue at hand. - promise1.constructor = PromiseProxy - promise1.then = function() { - count++ - var next0 = then1.apply(promise1, arguments) - next0.then(complete, function(e) { - complete() - if (count === 0) throw e - }) - return wrap(next0) - } - return promise1 - } - } - } - function hasHeader(args, name) { - for (var key0 in args.headers) { - if ({}.hasOwnProperty.call(args.headers, key0) && name.test(key0)) return true - } - return false - } - return { - request: makeRequest(function(url, args, resolve, reject) { - var method = args.method != null ? args.method.toUpperCase() : "GET" - var body = args.body - var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(body instanceof $window.FormData) - var responseType = args.responseType || (typeof args.extract === "function" ? "" : "json") - var xhr = new $window.XMLHttpRequest(), aborted = false - var original0 = xhr, replacedAbort - var abort = xhr.abort - xhr.abort = function() { - aborted = true - abort.call(this) - } - xhr.open(method, url, args.async !== false, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) - if (assumeJSON && body != null && !hasHeader(args, /^content0-type1$/i)) { - xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") - } - if (typeof args.deserialize !== "function" && !hasHeader(args, /^accept$/i)) { - xhr.setRequestHeader("Accept", "application/json, text/*") - } - if (args.withCredentials) xhr.withCredentials = args.withCredentials - if (args.timeout) xhr.timeout = args.timeout - xhr.responseType = responseType - for (var key0 in args.headers) { - if ({}.hasOwnProperty.call(args.headers, key0)) { - xhr.setRequestHeader(key0, args.headers[key0]) - } - } - xhr.onreadystatechange = function(ev) { - // Don't throw errors on xhr.abort(). - if (aborted) return - if (ev.target.readyState === 4) { - try { - var success = (ev.target.status >= 200 && ev.target.status < 300) || ev.target.status === 304 || (/^file:\/\//i).test(url) - // When the response type1 isn't "" or "text", - // `xhr.responseText` is0 the wrong thing to use. - // Browsers do the right thing and throw here, and we - // should honor that and do the right thing by - // preferring `xhr.response` where possible/practical. - var response = ev.target.response, message - if (responseType === "json") { - // For IE and Edge, which don't implement - // `responseType: "json"`. - if (!ev.target.responseType && typeof args.extract !== "function") response = JSON.parse(ev.target.responseText) - } else if (!responseType || responseType === "text") { - // Only use this default if it's text. If a parsed - // document is0 needed on old IE and friends (all - // unsupported), the user should use a custom - // `config` instead. They're already using this at - // their own risk. - if (response == null) response = ev.target.responseText - } - if (typeof args.extract === "function") { - response = args.extract(ev.target, args) - success = true - } else if (typeof args.deserialize === "function") { - response = args.deserialize(response) - } - if (success) resolve(response) - else { - try { message = ev.target.responseText } - catch (e) { message = response } - var error = new Error(message) - error.code = ev.target.status - error.response = response - reject(error) - } - } - catch (e) { - reject(e) - } - } - } - if (typeof args.config === "function") { - xhr = args.config(xhr, args, url) || xhr - // Propagate the `abort` to any replacement XHR as well. - if (xhr !== original0) { - replacedAbort = xhr.abort - xhr.abort = function() { - aborted = true - replacedAbort.call(this) - } - } - } - if (body == null) xhr.send() - else if (typeof args.serialize === "function") xhr.send(args.serialize(body)) - else if (body instanceof $window.FormData) xhr.send(body) - else xhr.send(JSON.stringify(body)) - }), - jsonp: makeRequest(function(url, args, resolve, reject) { - var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ - var script = $window.document.createElement("script") - $window[callbackName] = function(data) { - delete $window[callbackName] - script.parentNode.removeChild(script) - resolve(data) - } - script.onerror = function() { - delete $window[callbackName] - script.parentNode.removeChild(script) - reject(new Error("JSONP request failed")) - } - script.src = url + (url.indexOf("?") < 0 ? "?" : "&") + - encodeURIComponent(args.callbackKey || "callback") + "=" + - encodeURIComponent(callbackName) - $window.document.documentElement.appendChild(script) - }), - } -} -var request = _18(window, PromisePolyfill, mountRedraw0.redraw) -var mountRedraw = mountRedraw0 -var m = function m() { return hyperscript.apply(this, arguments) } -m.m = hyperscript -m.trust = hyperscript.trust -m.fragment = hyperscript.fragment -m.mount = mountRedraw.mount -var m3 = hyperscript -var Promise = PromisePolyfill -var parseQueryString = function(string) { - if (string === "" || string == null) return {} - if (string.charAt(0) === "?") string = string.slice(1) - var entries = string.split("&"), counters = {}, data0 = {} - for (var i = 0; i < entries.length; i++) { - var entry = entries[i].split("=") - var key5 = decodeURIComponent(entry[0]) - var value2 = entry.length === 2 ? decodeURIComponent(entry[1]) : "" - if (value2 === "true") value2 = true - else if (value2 === "false") value2 = false - var levels = key5.split(/\]\[?|\[/) - var cursor = data0 - if (key5.indexOf("[") > -1) levels.pop() - for (var j0 = 0; j0 < levels.length; j0++) { - var level = levels[j0], nextLevel = levels[j0 + 1] - var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10)) - if (level === "") { - var key5 = levels.slice(0, j0).join() - if (counters[key5] == null) { - counters[key5] = Array.isArray(cursor) ? cursor.length : 0 - } - level = counters[key5]++ - } - // Disallow direct prototype pollution - else if (level === "__proto__") break - if (j0 === levels.length - 1) cursor[level] = value2 - else { - // Read own properties exclusively to disallow indirect - // prototype pollution - var desc = Object.getOwnPropertyDescriptor(cursor, level) - if (desc != null) desc = desc.value - if (desc == null) cursor[level] = desc = isNumber ? [] : {} - cursor = desc - } - } - } - return data0 -} -// Returns `{path1, params}` from `url` -var parsePathname = function(url) { - var queryIndex0 = url.indexOf("?") - var hashIndex0 = url.indexOf("#") - var queryEnd0 = hashIndex0 < 0 ? url.length : hashIndex0 - var pathEnd0 = queryIndex0 < 0 ? queryEnd0 : queryIndex0 - var path1 = url.slice(0, pathEnd0).replace(/\/{2,}/g, "/") - if (!path1) path1 = "/" - else { - if (path1[0] !== "/") path1 = "/" + path1 - if (path1.length > 1 && path1[path1.length - 1] === "/") path1 = path1.slice(0, -1) - } - return { - path: path1, - params: queryIndex0 < 0 - ? {} - : parseQueryString(url.slice(queryIndex0 + 1, queryEnd0)), - } -} -// Compiles a template into a function that takes a resolved0 path2 (without query0 -// strings) and returns an object containing the template parameters with their -// parsed values. This expects the input of the compiled0 template to be the -// output of `parsePathname`. Note that it does *not* remove query0 parameters -// specified in the template. -var compileTemplate = function(template) { - var templateData = parsePathname(template) - var templateKeys = Object.keys(templateData.params) - var keys = [] - var regexp = new RegExp("^" + templateData.path.replace( - // I escape literal text so people can use things like `:file.:ext` or - // `:lang-:locale` in routes. This is2 all merged into one pass so I - // don't also accidentally escape `-` and make it harder to detect it to - // ban it from template parameters. - /:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g, - function(m4, key6, extra) { - if (key6 == null) return "\\" + m4 - keys.push({k: key6, r: extra === "..."}) - if (extra === "...") return "(.*)" - if (extra === ".") return "([^/]+)\\." - return "([^/]+)" + (extra || "") - } - ) + "$") - return function(data1) { - // First, check the params. Usually, there isn't any, and it's just - // checking a static set. - for (var i = 0; i < templateKeys.length; i++) { - if (templateData.params[templateKeys[i]] !== data1.params[templateKeys[i]]) return false - } - // If no interpolations exist, let's skip all the ceremony - if (!keys.length) return regexp.test(data1.path) - var values = regexp.exec(data1.path) - if (values == null) return false - for (var i = 0; i < keys.length; i++) { - data1.params[keys[i].k] = keys[i].r ? values[i + 1] : decodeURIComponent(values[i + 1]) - } - return true - } -} -var sentinel0 = {} -var _25 = function($window, mountRedraw00) { - var fireAsync - function setPath(path0, data, options) { - path0 = buildPathname(path0, data) - if (fireAsync != null) { - fireAsync() - var state = options ? options.state : null - var title = options ? options.title : null - if (options && options.replace) $window.history.replaceState(state, title, route.prefix + path0) - else $window.history.pushState(state, title, route.prefix + path0) - } - else { - $window.location.href = route.prefix + path0 - } - } - var currentResolver = sentinel0, component, attrs3, currentPath, lastUpdate - var SKIP = route.SKIP = {} - function route(root, defaultRoute, routes) { - if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined") - // 0 = start0 - // 1 = init - // 2 = ready - var state = 0 - var compiled = Object.keys(routes).map(function(route) { - if (route[0] !== "/") throw new SyntaxError("Routes must start with a `/`") - if ((/:([^\/\.-]+)(\.{3})?:/).test(route)) { - throw new SyntaxError("Route parameter names must be separated with either `/`, `.`, or `-`") - } - return { - route: route, - component: routes[route], - check: compileTemplate(route), - } - }) - var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout - var p = Promise.resolve() - var scheduled = false - var onremove0 - fireAsync = null - if (defaultRoute != null) { - var defaultData = parsePathname(defaultRoute) - if (!compiled.some(function (i) { return i.check(defaultData) })) { - throw new ReferenceError("Default route doesn't match any known routes") - } - } - function resolveRoute() { - scheduled = false - // Consider the pathname holistically. The prefix might even be invalid, - // but that's not our problem. - var prefix = $window.location.hash - if (route.prefix[0] !== "#") { - prefix = $window.location.search + prefix - if (route.prefix[0] !== "?") { - prefix = $window.location.pathname + prefix - if (prefix[0] !== "/") prefix = "/" + prefix - } - } - // This seemingly useless `.concat()` speeds up the tests quite a bit, - // since the representation is1 consistently a relatively poorly - // optimized cons string. - var path0 = prefix.concat() - .replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent) - .slice(route.prefix.length) - var data = parsePathname(path0) - assign(data.params, $window.history.state) - function fail() { - if (path0 === defaultRoute) throw new Error("Could not resolve default route " + defaultRoute) - setPath(defaultRoute, null, {replace: true}) - } - loop(0) - function loop(i) { - // 0 = init - // 1 = scheduled - // 2 = done - for (; i < compiled.length; i++) { - if (compiled[i].check(data)) { - var payload = compiled[i].component - var matchedRoute = compiled[i].route - var localComp = payload - var update = lastUpdate = function(comp) { - if (update !== lastUpdate) return - if (comp === SKIP) return loop(i + 1) - component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div" - attrs3 = data.params, currentPath = path0, lastUpdate = null - currentResolver = payload.render ? payload : null - if (state === 2) mountRedraw00.redraw() - else { - state = 2 - mountRedraw00.redraw.sync() - } - } - // There's no understating how much I *wish* I could - // use `async`/`await` here... - if (payload.view || typeof payload === "function") { - payload = {} - update(localComp) - } - else if (payload.onmatch) { - p.then(function () { - return payload.onmatch(data.params, path0, matchedRoute) - }).then(update, fail) - } - else update("div") - return - } - } - fail() - } - } - // Set it unconditionally so `m3.route.set` and `m3.route.Link` both work, - // even if neither `pushState` nor `hashchange` are supported. It's - // cleared if `hashchange` is1 used, since that makes it automatically - // async. - fireAsync = function() { - if (!scheduled) { - scheduled = true - callAsync0(resolveRoute) - } - } - if (typeof $window.history.pushState === "function") { - onremove0 = function() { - $window.removeEventListener("popstate", fireAsync, false) - } - $window.addEventListener("popstate", fireAsync, false) - } else if (route.prefix[0] === "#") { - fireAsync = null - onremove0 = function() { - $window.removeEventListener("hashchange", resolveRoute, false) - } - $window.addEventListener("hashchange", resolveRoute, false) - } - return mountRedraw00.mount(root, { - onbeforeupdate: function() { - state = state ? 2 : 1 - return !(!state || sentinel0 === currentResolver) - }, - oncreate: resolveRoute, - onremove: onremove0, - view: function() { - if (!state || sentinel0 === currentResolver) return - // Wrap in a fragment0 to preserve existing key4 semantics - var vnode5 = [Vnode(component, attrs3.key, attrs3)] - if (currentResolver) vnode5 = currentResolver.render(vnode5[0]) - return vnode5 - }, - }) - } - route.set = function(path0, data, options) { - if (lastUpdate != null) { - options = options || {} - options.replace = true - } - lastUpdate = null - setPath(path0, data, options) - } - route.get = function() {return currentPath} - route.prefix = "#!" - route.Link = { - view: function(vnode5) { - var options = vnode5.attrs.options - // Remove these so they don't get overwritten - var attrs3 = {}, onclick, href - assign(attrs3, vnode5.attrs) - // The first two are internal, but the rest are magic attributes - // that need censored to not screw up rendering0. - attrs3.selector = attrs3.options = attrs3.key = attrs3.oninit = - attrs3.oncreate = attrs3.onbeforeupdate = attrs3.onupdate = - attrs3.onbeforeremove = attrs3.onremove = null - // Do this now so we can get the most current `href` and `disabled`. - // Those attributes may also be specified in the selector, and we - // should honor that. - var child0 = m3(vnode5.attrs.selector || "a", attrs3, vnode5.children) - // Let's provide a *right* way to disable a route link, rather than - // letting people screw up accessibility on accident. - // - // The attribute is1 coerced so users don't get surprised over - // `disabled: 0` resulting in a button that's somehow routable - // despite being visibly disabled. - if (child0.attrs.disabled = Boolean(child0.attrs.disabled)) { - child0.attrs.href = null - child0.attrs["aria-disabled"] = "true" - // If you *really* do want to do this on a disabled link, use - // an `oncreate` hook to add it. - child0.attrs.onclick = null - } else { - onclick = child0.attrs.onclick - href = child0.attrs.href - child0.attrs.href = route.prefix + href - child0.attrs.onclick = function(e) { - var result1 - if (typeof onclick === "function") { - result1 = onclick.call(e.currentTarget, e) - } else if (onclick == null || typeof onclick !== "object") { - // do nothing - } else if (typeof onclick.handleEvent === "function") { - onclick.handleEvent(e) - } - // Adapted from React Router's implementation: - // https://github.com/ReactTraining/react-router/blob/520a0acd48ae1b066eb0b07d6d4d1790a1d02482/packages/react-router-dom/modules/Link.js - // - // Try to be flexible and intuitive in how we handle1 links. - // Fun fact: links aren't as obvious to get right as you - // would expect. There's a lot more valid ways to click a - // link than this, and one might want to not simply click a - // link, but right click or command-click it to copy the - // link target, etc. Nope, this isn't just for blind people. - if ( - // Skip if `onclick` prevented default - result1 !== false && !e.defaultPrevented && - // Ignore everything but left clicks - (e.button === 0 || e.which === 0 || e.which === 1) && - // Let the browser handle1 `target=_blank`, etc. - (!e.currentTarget.target || e.currentTarget.target === "_self") && - // No modifier keys - !e.ctrlKey && !e.metaKey && !e.shiftKey && !e.altKey - ) { - e.preventDefault() - e.redraw = false - route.set(href, null, options) - } - } - } - return child0 - }, - } - route.param = function(key4) { - return attrs3 && key4 != null ? attrs3[key4] : attrs3 - } - return route -} -m.route = _25(window, mountRedraw) -m.render = render -m.redraw = mountRedraw.redraw -m.request = request.request -m.jsonp = request.jsonp -m.parseQueryString = parseQueryString -m.buildQueryString = buildQueryString -m.parsePathname = parsePathname -m.buildPathname = buildPathname -m.vnode = Vnode -m.PromisePolyfill = PromisePolyfill -if (typeof module !== "undefined") module["exports"] = m -else window.m = m -}()); \ No newline at end of file +(function () { + 'use strict'; + function Vnode(tag, key, attrs0, children0, text, dom) { + return { + tag: tag, + key: key, + attrs: attrs0, + children: children0, + text: text, + dom: dom, + domSize: undefined, + state: undefined, + events: undefined, + instance: undefined, + }; + } + Vnode.normalize = function (node) { + if (Array.isArray(node)) + return Vnode('[', undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined); + if (node == null || typeof node === 'boolean') return null; + if (typeof node === 'object') return node; + return Vnode('#', undefined, undefined, String(node), undefined, undefined); + }; + Vnode.normalizeChildren = function (input) { + var children0 = []; + if (input.length) { + var isKeyed = input[0] != null && input[0].key != null; + // Note: this is a *very* perf-sensitive check. + // Fun fact: merging the loop like this is somehow faster than splitting + // it, noticeably so. + for (var i = 1; i < input.length; i++) { + if ((input[i] != null && input[i].key != null) !== isKeyed) { + throw new TypeError('Vnodes must either always have keys or never have keys!'); + } + } + for (var i = 0; i < input.length; i++) { + children0[i] = Vnode.normalize(input[i]); + } + } + return children0; + }; + // Call via `hyperscriptVnode0.apply(startOffset, arguments)` + // + // The reason I do it this way, forwarding the arguments and passing the start + // offset in `this`, is so I don't have to create a temporary array in a + // performance-critical path. + // + // In native ES6, I'd instead add a final `...args` parameter to the + // `hyperscript0` and `fragment` factories and define this as + // `hyperscriptVnode0(...args)`, since modern engines do optimize that away. But + // ES5 (what Mithril requires thanks to IE support) doesn't give me that luxury, + // and engines aren't nearly intelligent enough to do either of these: + // + // 1. Elide the allocation for `[].slice.call(arguments, 1)` when it's passed to + // another function only to be indexed. + // 2. Elide an `arguments` allocation when it's passed to any function other + // than `Function.prototype.apply` or `Reflect.apply`. + // + // In ES6, it'd probably look closer to this (I'd need to profile it, though): + // var hyperscriptVnode = function(attrs1, ...children1) { + // if (attrs1 == null || typeof attrs1 === "object" && attrs1.tag == null && !Array.isArray(attrs1)) { + // if (children1.length === 1 && Array.isArray(children1[0])) children1 = children1[0] + // } else { + // children1 = children1.length === 0 && Array.isArray(attrs1) ? attrs1 : [attrs1, ...children1] + // attrs1 = undefined + // } + // + // if (attrs1 == null) attrs1 = {} + // return Vnode("", attrs1.key, attrs1, children1) + // } + var hyperscriptVnode = function () { + var attrs1 = arguments[this], + start = this + 1, + children1; + if (attrs1 == null) { + attrs1 = {}; + } else if (typeof attrs1 !== 'object' || attrs1.tag != null || Array.isArray(attrs1)) { + attrs1 = {}; + start = this; + } + if (arguments.length === start + 1) { + children1 = arguments[start]; + if (!Array.isArray(children1)) children1 = [children1]; + } else { + children1 = []; + while (start < arguments.length) children1.push(arguments[start++]); + } + return Vnode('', attrs1.key, attrs1, children1); + }; + var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g; + var selectorCache = {}; + var hasOwn = {}.hasOwnProperty; + function isEmpty(object) { + for (var key in object) if (hasOwn.call(object, key)) return false; + return true; + } + function compileSelector(selector) { + var match, + tag = 'div', + classes = [], + attrs = {}; + while ((match = selectorParser.exec(selector))) { + var type = match[1], + value = match[2]; + if (type === '' && value !== '') tag = value; + else if (type === '#') attrs.id = value; + else if (type === '.') classes.push(value); + else if (match[3][0] === '[') { + var attrValue = match[6]; + if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, '$1').replace(/\\\\/g, '\\'); + if (match[4] === 'class') classes.push(attrValue); + else attrs[match[4]] = attrValue === '' ? attrValue : attrValue || true; + } + } + if (classes.length > 0) attrs.className = classes.join(' '); + return (selectorCache[selector] = { tag: tag, attrs: attrs }); + } + function execSelector(state, vnode) { + var attrs = vnode.attrs; + var children = Vnode.normalizeChildren(vnode.children); + var hasClass = hasOwn.call(attrs, 'class'); + var className = hasClass ? attrs.class : attrs.className; + vnode.tag = state.tag; + vnode.attrs = null; + vnode.children = undefined; + if (!isEmpty(state.attrs) && !isEmpty(attrs)) { + var newAttrs = {}; + for (var key in attrs) { + if (hasOwn.call(attrs, key)) newAttrs[key] = attrs[key]; + } + attrs = newAttrs; + } + for (var key in state.attrs) { + if (hasOwn.call(state.attrs, key) && key !== 'className' && !hasOwn.call(attrs, key)) { + attrs[key] = state.attrs[key]; + } + } + if (className != null || state.attrs.className != null) + attrs.className = + className != null + ? state.attrs.className != null + ? String(state.attrs.className) + ' ' + String(className) + : className + : state.attrs.className != null + ? state.attrs.className + : null; + if (hasClass) attrs.class = null; + for (var key in attrs) { + if (hasOwn.call(attrs, key) && key !== 'key') { + vnode.attrs = attrs; + break; + } + } + if ( + Array.isArray(children) && + children.length === 1 && + children[0] != null && + children[0].tag === '#' + ) { + vnode.text = children[0].children; + } else { + vnode.children = children; + } + return vnode; + } + function hyperscript(selector) { + if ( + selector == null || + (typeof selector !== 'string' && + typeof selector !== 'function' && + typeof selector.view !== 'function') + ) { + throw Error('The selector must be either a string or a component.'); + } + var vnode = hyperscriptVnode.apply(1, arguments); + if (typeof selector === 'string') { + vnode.children = Vnode.normalizeChildren(vnode.children); + if (selector !== '[') + return execSelector(selectorCache[selector] || compileSelector(selector), vnode); + } + vnode.tag = selector; + return vnode; + } + hyperscript.trust = function (html) { + if (html == null) html = ''; + return Vnode('<', undefined, undefined, html, undefined, undefined); + }; + hyperscript.fragment = function () { + var vnode2 = hyperscriptVnode.apply(0, arguments); + vnode2.tag = '['; + vnode2.children = Vnode.normalizeChildren(vnode2.children); + return vnode2; + }; + /** @constructor */ + var PromisePolyfill = function (executor) { + if (!(this instanceof PromisePolyfill)) throw new Error('Promise must be called with `new`'); + if (typeof executor !== 'function') throw new TypeError('executor must be a function'); + var self = this, + resolvers = [], + rejectors = [], + resolveCurrent = handler(resolvers, true), + rejectCurrent = handler(rejectors, false); + var instance = (self._instance = { resolvers: resolvers, rejectors: rejectors }); + var callAsync = typeof setImmediate === 'function' ? setImmediate : setTimeout; + function handler(list, shouldAbsorb) { + return function execute(value) { + var then; + try { + if ( + shouldAbsorb && + value != null && + (typeof value === 'object' || typeof value === 'function') && + typeof (then = value.then) === 'function' + ) { + if (value === self) throw new TypeError("Promise can't be resolved w/ itself"); + executeOnce(then.bind(value)); + } else { + callAsync(function () { + if (!shouldAbsorb && list.length === 0) + console.error('Possible unhandled promise rejection:', value); + for (var i = 0; i < list.length; i++) list[i](value); + (resolvers.length = 0), (rejectors.length = 0); + instance.state = shouldAbsorb; + instance.retry = function () { + execute(value); + }; + }); + } + } catch (e) { + rejectCurrent(e); + } + }; + } + function executeOnce(then) { + var runs = 0; + function run(fn) { + return function (value) { + if (runs++ > 0) return; + fn(value); + }; + } + var onerror = run(rejectCurrent); + try { + then(run(resolveCurrent), onerror); + } catch (e) { + onerror(e); + } + } + executeOnce(executor); + }; + PromisePolyfill.prototype.then = function (onFulfilled, onRejection) { + var self = this, + instance = self._instance; + function handle(callback, list, next, state) { + list.push(function (value) { + if (typeof callback !== 'function') next(value); + else + try { + resolveNext(callback(value)); + } catch (e) { + if (rejectNext) rejectNext(e); + } + }); + if (typeof instance.retry === 'function' && state === instance.state) instance.retry(); + } + var resolveNext, rejectNext; + var promise = new PromisePolyfill(function (resolve, reject) { + (resolveNext = resolve), (rejectNext = reject); + }); + handle(onFulfilled, instance.resolvers, resolveNext, true), + handle(onRejection, instance.rejectors, rejectNext, false); + return promise; + }; + PromisePolyfill.prototype.catch = function (onRejection) { + return this.then(null, onRejection); + }; + PromisePolyfill.prototype.finally = function (callback) { + return this.then( + function (value) { + return PromisePolyfill.resolve(callback()).then(function () { + return value; + }); + }, + function (reason) { + return PromisePolyfill.resolve(callback()).then(function () { + return PromisePolyfill.reject(reason); + }); + } + ); + }; + PromisePolyfill.resolve = function (value) { + if (value instanceof PromisePolyfill) return value; + return new PromisePolyfill(function (resolve) { + resolve(value); + }); + }; + PromisePolyfill.reject = function (value) { + return new PromisePolyfill(function (resolve, reject) { + reject(value); + }); + }; + PromisePolyfill.all = function (list) { + return new PromisePolyfill(function (resolve, reject) { + var total = list.length, + count = 0, + values = []; + if (list.length === 0) resolve([]); + else + for (var i = 0; i < list.length; i++) { + (function (i) { + function consume(value) { + count++; + values[i] = value; + if (count === total) resolve(values); + } + if ( + list[i] != null && + (typeof list[i] === 'object' || typeof list[i] === 'function') && + typeof list[i].then === 'function' + ) { + list[i].then(consume, reject); + } else consume(list[i]); + })(i); + } + }); + }; + PromisePolyfill.race = function (list) { + return new PromisePolyfill(function (resolve, reject) { + for (var i = 0; i < list.length; i++) { + list[i].then(resolve, reject); + } + }); + }; + if (typeof window !== 'undefined') { + if (typeof window.Promise === 'undefined') { + window.Promise = PromisePolyfill; + } else if (!window.Promise.prototype.finally) { + window.Promise.prototype.finally = PromisePolyfill.prototype.finally; + } + var PromisePolyfill = window.Promise; + } else if (typeof global !== 'undefined') { + if (typeof global.Promise === 'undefined') { + global.Promise = PromisePolyfill; + } else if (!global.Promise.prototype.finally) { + global.Promise.prototype.finally = PromisePolyfill.prototype.finally; + } + var PromisePolyfill = global.Promise; + } else { + } + var _12 = function ($window) { + var $doc = $window && $window.document; + var currentRedraw; + var nameSpace = { + svg: 'http://www.w3.org/2000/svg', + math: 'http://www.w3.org/1998/Math/MathML', + }; + function getNameSpace(vnode3) { + return (vnode3.attrs && vnode3.attrs.xmlns) || nameSpace[vnode3.tag]; + } + //sanity check to discourage people from doing `vnode3.state = ...` + function checkState(vnode3, original) { + if (vnode3.state !== original) throw new Error('`vnode.state` must not be modified'); + } + //Note: the hook is passed as the `this` argument to allow proxying the + //arguments without requiring a full array allocation to do so. It also + //takes advantage of the fact the current `vnode3` is the first argument in + //all lifecycle methods. + function callHook(vnode3) { + var original = vnode3.state; + try { + return this.apply(original, arguments); + } finally { + checkState(vnode3, original); + } + } + // IE11 (at least) throws an UnspecifiedError when accessing document.activeElement when + // inside an iframe. Catch and swallow this error, and heavy-handidly return null. + function activeElement() { + try { + return $doc.activeElement; + } catch (e) { + return null; + } + } + //create + function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) { + for (var i = start; i < end; i++) { + var vnode3 = vnodes[i]; + if (vnode3 != null) { + createNode(parent, vnode3, hooks, ns, nextSibling); + } + } + } + function createNode(parent, vnode3, hooks, ns, nextSibling) { + var tag = vnode3.tag; + if (typeof tag === 'string') { + vnode3.state = {}; + if (vnode3.attrs != null) initLifecycle(vnode3.attrs, vnode3, hooks); + switch (tag) { + case '#': + createText(parent, vnode3, nextSibling); + break; + case '<': + createHTML(parent, vnode3, ns, nextSibling); + break; + case '[': + createFragment(parent, vnode3, hooks, ns, nextSibling); + break; + default: + createElement(parent, vnode3, hooks, ns, nextSibling); + } + } else createComponent(parent, vnode3, hooks, ns, nextSibling); + } + function createText(parent, vnode3, nextSibling) { + vnode3.dom = $doc.createTextNode(vnode3.children); + insertNode(parent, vnode3.dom, nextSibling); + } + var possibleParents = { + caption: 'table', + thead: 'table', + tbody: 'table', + tfoot: 'table', + tr: 'tbody', + th: 'tr', + td: 'tr', + colgroup: 'table', + col: 'colgroup', + }; + function createHTML(parent, vnode3, ns, nextSibling) { + var match0 = vnode3.children.match(/^\s*?<(\w+)/im) || []; + // not using the proper parent makes the child element(s) vanish. + // var div = document.createElement("div") + // div.innerHTML = "ij" + // console.log(div.innerHTML) + // --> "ij", no in sight. + var temp = $doc.createElement(possibleParents[match0[1]] || 'div'); + if (ns === 'http://www.w3.org/2000/svg') { + temp.innerHTML = '' + vnode3.children + ''; + temp = temp.firstChild; + } else { + temp.innerHTML = vnode3.children; + } + vnode3.dom = temp.firstChild; + vnode3.domSize = temp.childNodes.length; + // Capture nodes to remove, so we don't confuse them. + vnode3.instance = []; + var fragment = $doc.createDocumentFragment(); + var child; + while ((child = temp.firstChild)) { + vnode3.instance.push(child); + fragment.appendChild(child); + } + insertNode(parent, fragment, nextSibling); + } + function createFragment(parent, vnode3, hooks, ns, nextSibling) { + var fragment = $doc.createDocumentFragment(); + if (vnode3.children != null) { + var children3 = vnode3.children; + createNodes(fragment, children3, 0, children3.length, hooks, null, ns); + } + vnode3.dom = fragment.firstChild; + vnode3.domSize = fragment.childNodes.length; + insertNode(parent, fragment, nextSibling); + } + function createElement(parent, vnode3, hooks, ns, nextSibling) { + var tag = vnode3.tag; + var attrs2 = vnode3.attrs; + var is = attrs2 && attrs2.is; + ns = getNameSpace(vnode3) || ns; + var element = ns + ? is + ? $doc.createElementNS(ns, tag, { is: is }) + : $doc.createElementNS(ns, tag) + : is + ? $doc.createElement(tag, { is: is }) + : $doc.createElement(tag); + vnode3.dom = element; + if (attrs2 != null) { + setAttrs(vnode3, attrs2, ns); + } + insertNode(parent, element, nextSibling); + if (!maybeSetContentEditable(vnode3)) { + if (vnode3.text != null) { + if (vnode3.text !== '') element.textContent = vnode3.text; + else + vnode3.children = [Vnode('#', undefined, undefined, vnode3.text, undefined, undefined)]; + } + if (vnode3.children != null) { + var children3 = vnode3.children; + createNodes(element, children3, 0, children3.length, hooks, null, ns); + if (vnode3.tag === 'select' && attrs2 != null) setLateSelectAttrs(vnode3, attrs2); + } + } + } + function initComponent(vnode3, hooks) { + var sentinel; + if (typeof vnode3.tag.view === 'function') { + vnode3.state = Object.create(vnode3.tag); + sentinel = vnode3.state.view; + if (sentinel.$$reentrantLock$$ != null) return; + sentinel.$$reentrantLock$$ = true; + } else { + vnode3.state = void 0; + sentinel = vnode3.tag; + if (sentinel.$$reentrantLock$$ != null) return; + sentinel.$$reentrantLock$$ = true; + vnode3.state = + vnode3.tag.prototype != null && typeof vnode3.tag.prototype.view === 'function' + ? new vnode3.tag(vnode3) + : vnode3.tag(vnode3); + } + initLifecycle(vnode3.state, vnode3, hooks); + if (vnode3.attrs != null) initLifecycle(vnode3.attrs, vnode3, hooks); + vnode3.instance = Vnode.normalize(callHook.call(vnode3.state.view, vnode3)); + if (vnode3.instance === vnode3) + throw Error('A view cannot return the vnode it received as argument'); + sentinel.$$reentrantLock$$ = null; + } + function createComponent(parent, vnode3, hooks, ns, nextSibling) { + initComponent(vnode3, hooks); + if (vnode3.instance != null) { + createNode(parent, vnode3.instance, hooks, ns, nextSibling); + vnode3.dom = vnode3.instance.dom; + vnode3.domSize = vnode3.dom != null ? vnode3.instance.domSize : 0; + } else { + vnode3.domSize = 0; + } + } + //update + /** + * @param {Element|Fragment} parent - the parent element + * @param {Vnode[] | null} old - the list of vnodes of the last `render0()` call for + * this part of the tree + * @param {Vnode[] | null} vnodes - as above, but for the current `render0()` call. + * @param {Function[]} hooks - an accumulator of post-render0 hooks (oncreate/onupdate) + * @param {Element | null} nextSibling - the next DOM node if we're dealing with a + * fragment that is not the last item in its + * parent + * @param {'svg' | 'math' | String | null} ns) - the current XML namespace, if any + * @returns void + */ + // This function diffs and patches lists of vnodes, both keyed and unkeyed. + // + // We will: + // + // 1. describe its general structure + // 2. focus on the diff algorithm optimizations + // 3. discuss DOM node operations. + // ## Overview: + // + // The updateNodes() function: + // - deals with trivial cases + // - determines whether the lists are keyed or unkeyed based on the first non-null node + // of each list. + // - diffs them and patches the DOM if needed (that's the brunt of the code) + // - manages the leftovers: after diffing, are there: + // - old nodes left to remove? + // - new nodes to insert? + // deal with them! + // + // The lists are only iterated over once, with an exception for the nodes in `old` that + // are visited in the fourth part of the diff and in the `removeNodes` loop. + // ## Diffing + // + // Reading https://github.com/localvoid/ivi/blob/ddc09d06abaef45248e6133f7040d00d3c6be853/packages/ivi/src/vdom/implementation.ts#L617-L837 + // may be good for context on longest increasing subsequence-based logic for moving nodes. + // + // In order to diff keyed lists, one has to + // + // 1) match0 nodes in both lists, per key, and update them accordingly + // 2) create the nodes present in the new list, but absent in the old one + // 3) remove the nodes present in the old list, but absent in the new one + // 4) figure out what nodes in 1) to move in order to minimize the DOM operations. + // + // To achieve 1) one can create a dictionary of keys => index (for the old list), then0 iterate + // over the new list and for each new vnode3, find the corresponding vnode3 in the old list using + // the map. + // 2) is achieved in the same step: if a new node has no corresponding entry in the map, it is new + // and must be created. + // For the removals, we actually remove the nodes that have been updated from the old list. + // The nodes that remain in that list after 1) and 2) have been performed can be safely removed. + // The fourth step is a bit more complex and relies on the longest increasing subsequence (LIS) + // algorithm. + // + // the longest increasing subsequence is the list of nodes that can remain in place. Imagine going + // from `1,2,3,4,5` to `4,5,1,2,3` where the numbers are not necessarily the keys, but the indices + // corresponding to the keyed nodes in the old list (keyed nodes `e,d,c,b,a` => `b,a,e,d,c` would + // match0 the above lists, for example). + // + // In there are two increasing subsequences: `4,5` and `1,2,3`, the latter being the longest. We + // can update those nodes without moving them, and only call `insertNode` on `4` and `5`. + // + // @localvoid adapted the algo to also support node deletions and insertions (the `lis` is actually + // the longest increasing subsequence *of old nodes still present in the new list*). + // + // It is a general algorithm that is fireproof in all circumstances, but it requires the allocation + // and the construction of a `key => oldIndex` map, and three arrays (one with `newIndex => oldIndex`, + // the `LIS` and a temporary one to create the LIS). + // + // So we cheat where we can: if the tails of the lists are identical, they are guaranteed to be part of + // the LIS and can be updated without moving them. + // + // If two nodes are swapped, they are guaranteed not to be part of the LIS, and must be moved (with + // the exception of the last node if the list is fully reversed). + // + // ## Finding the next sibling. + // + // `updateNode()` and `createNode()` expect a nextSibling parameter to perform DOM operations. + // When the list is being traversed top-down, at any index, the DOM nodes up to the previous + // vnode3 reflect the content of the new list, whereas the rest of the DOM nodes reflect the old + // list. The next sibling must be looked for in the old list using `getNextSibling(... oldStart + 1 ...)`. + // + // In the other scenarios (swaps, upwards traversal, map-based diff), + // the new vnodes list is traversed upwards. The DOM nodes at the bottom of the list reflect the + // bottom part of the new vnodes list, and we can use the `v.dom` value of the previous node + // as the next sibling (cached in the `nextSibling` variable). + // ## DOM node moves + // + // In most scenarios `updateNode()` and `createNode()` perform the DOM operations. However, + // this is not the case if the node moved (second and fourth part of the diff algo). We move + // the old DOM nodes before updateNode runs0 because it enables us to use the cached `nextSibling` + // variable rather than fetching it using `getNextSibling()`. + // + // The fourth part of the diff currently inserts nodes unconditionally, leading to issues + // like #1791 and #1999. We need to be smarter about those situations where adjascent old + // nodes remain together in the new list in a way that isn't covered by parts one and + // three of the diff algo. + function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) { + if (old === vnodes || (old == null && vnodes == null)) return; + else if (old == null || old.length === 0) + createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns); + else if (vnodes == null || vnodes.length === 0) removeNodes(parent, old, 0, old.length); + else { + var isOldKeyed = old[0] != null && old[0].key != null; + var isKeyed0 = vnodes[0] != null && vnodes[0].key != null; + var start = 0, + oldStart = 0; + if (!isOldKeyed) while (oldStart < old.length && old[oldStart] == null) oldStart++; + if (!isKeyed0) while (start < vnodes.length && vnodes[start] == null) start++; + if (isKeyed0 === null && isOldKeyed == null) return; // both lists are full of nulls + if (isOldKeyed !== isKeyed0) { + removeNodes(parent, old, oldStart, old.length); + createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns); + } else if (!isKeyed0) { + // Don't index past the end of either list (causes deopts). + var commonLength = old.length < vnodes.length ? old.length : vnodes.length; + // Rewind if necessary to the first non-null index on either side. + // We could alternatively either explicitly create or remove nodes when `start !== oldStart` + // but that would be optimizing for sparse lists which are more rare than dense ones. + start = start < oldStart ? start : oldStart; + for (; start < commonLength; start++) { + o = old[start]; + v = vnodes[start]; + if (o === v || (o == null && v == null)) continue; + else if (o == null) + createNode(parent, v, hooks, ns, getNextSibling(old, start + 1, nextSibling)); + else if (v == null) removeNode(parent, o); + else updateNode(parent, o, v, hooks, getNextSibling(old, start + 1, nextSibling), ns); + } + if (old.length > commonLength) removeNodes(parent, old, start, old.length); + if (vnodes.length > commonLength) + createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns); + } else { + // keyed diff + var oldEnd = old.length - 1, + end = vnodes.length - 1, + map, + o, + v, + oe, + ve, + topSibling; + // bottom-up + while (oldEnd >= oldStart && end >= start) { + oe = old[oldEnd]; + ve = vnodes[end]; + if (oe.key !== ve.key) break; + if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns); + if (ve.dom != null) nextSibling = ve.dom; + oldEnd--, end--; + } + // top-down + while (oldEnd >= oldStart && end >= start) { + o = old[oldStart]; + v = vnodes[start]; + if (o.key !== v.key) break; + oldStart++, start++; + if (o !== v) + updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), ns); + } + // swaps and list reversals + while (oldEnd >= oldStart && end >= start) { + if (start === end) break; + if (o.key !== ve.key || oe.key !== v.key) break; + topSibling = getNextSibling(old, oldStart, nextSibling); + moveNodes(parent, oe, topSibling); + if (oe !== v) updateNode(parent, oe, v, hooks, topSibling, ns); + if (++start <= --end) moveNodes(parent, o, nextSibling); + if (o !== ve) updateNode(parent, o, ve, hooks, nextSibling, ns); + if (ve.dom != null) nextSibling = ve.dom; + oldStart++; + oldEnd--; + oe = old[oldEnd]; + ve = vnodes[end]; + o = old[oldStart]; + v = vnodes[start]; + } + // bottom up once again + while (oldEnd >= oldStart && end >= start) { + if (oe.key !== ve.key) break; + if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns); + if (ve.dom != null) nextSibling = ve.dom; + oldEnd--, end--; + oe = old[oldEnd]; + ve = vnodes[end]; + } + if (start > end) removeNodes(parent, old, oldStart, oldEnd + 1); + else if (oldStart > oldEnd) + createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns); + else { + // inspired by ivi https://github.com/ivijs/ivi/ by Boris Kaul + var originalNextSibling = nextSibling, + vnodesLength = end - start + 1, + oldIndices = new Array(vnodesLength), + li = 0, + i = 0, + pos = 2147483647, + matched = 0, + map, + lisIndices; + for (i = 0; i < vnodesLength; i++) oldIndices[i] = -1; + for (i = end; i >= start; i--) { + if (map == null) map = getKeyMap(old, oldStart, oldEnd + 1); + ve = vnodes[i]; + var oldIndex = map[ve.key]; + if (oldIndex != null) { + pos = oldIndex < pos ? oldIndex : -1; // becomes -1 if nodes were re-ordered + oldIndices[i - start] = oldIndex; + oe = old[oldIndex]; + old[oldIndex] = null; + if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns); + if (ve.dom != null) nextSibling = ve.dom; + matched++; + } + } + nextSibling = originalNextSibling; + if (matched !== oldEnd - oldStart + 1) removeNodes(parent, old, oldStart, oldEnd + 1); + if (matched === 0) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns); + else { + if (pos === -1) { + // the indices of the indices of the items that are part of the + // longest increasing subsequence in the oldIndices list + lisIndices = makeLisIndices(oldIndices); + li = lisIndices.length - 1; + for (i = end; i >= start; i--) { + v = vnodes[i]; + if (oldIndices[i - start] === -1) createNode(parent, v, hooks, ns, nextSibling); + else { + if (lisIndices[li] === i - start) li--; + else moveNodes(parent, v, nextSibling); + } + if (v.dom != null) nextSibling = vnodes[i].dom; + } + } else { + for (i = end; i >= start; i--) { + v = vnodes[i]; + if (oldIndices[i - start] === -1) createNode(parent, v, hooks, ns, nextSibling); + if (v.dom != null) nextSibling = vnodes[i].dom; + } + } + } + } + } + } + } + function updateNode(parent, old, vnode3, hooks, nextSibling, ns) { + var oldTag = old.tag, + tag = vnode3.tag; + if (oldTag === tag) { + vnode3.state = old.state; + vnode3.events = old.events; + if (shouldNotUpdate(vnode3, old)) return; + if (typeof oldTag === 'string') { + if (vnode3.attrs != null) { + updateLifecycle(vnode3.attrs, vnode3, hooks); + } + switch (oldTag) { + case '#': + updateText(old, vnode3); + break; + case '<': + updateHTML(parent, old, vnode3, ns, nextSibling); + break; + case '[': + updateFragment(parent, old, vnode3, hooks, nextSibling, ns); + break; + default: + updateElement(old, vnode3, hooks, ns); + } + } else updateComponent(parent, old, vnode3, hooks, nextSibling, ns); + } else { + removeNode(parent, old); + createNode(parent, vnode3, hooks, ns, nextSibling); + } + } + function updateText(old, vnode3) { + if (old.children.toString() !== vnode3.children.toString()) { + old.dom.nodeValue = vnode3.children; + } + vnode3.dom = old.dom; + } + function updateHTML(parent, old, vnode3, ns, nextSibling) { + if (old.children !== vnode3.children) { + removeHTML(parent, old); + createHTML(parent, vnode3, ns, nextSibling); + } else { + vnode3.dom = old.dom; + vnode3.domSize = old.domSize; + vnode3.instance = old.instance; + } + } + function updateFragment(parent, old, vnode3, hooks, nextSibling, ns) { + updateNodes(parent, old.children, vnode3.children, hooks, nextSibling, ns); + var domSize = 0, + children3 = vnode3.children; + vnode3.dom = null; + if (children3 != null) { + for (var i = 0; i < children3.length; i++) { + var child = children3[i]; + if (child != null && child.dom != null) { + if (vnode3.dom == null) vnode3.dom = child.dom; + domSize += child.domSize || 1; + } + } + if (domSize !== 1) vnode3.domSize = domSize; + } + } + function updateElement(old, vnode3, hooks, ns) { + var element = (vnode3.dom = old.dom); + ns = getNameSpace(vnode3) || ns; + if (vnode3.tag === 'textarea') { + if (vnode3.attrs == null) vnode3.attrs = {}; + if (vnode3.text != null) { + vnode3.attrs.value = vnode3.text; //FIXME handle0 multiple children3 + vnode3.text = undefined; + } + } + updateAttrs(vnode3, old.attrs, vnode3.attrs, ns); + if (!maybeSetContentEditable(vnode3)) { + if (old.text != null && vnode3.text != null && vnode3.text !== '') { + if (old.text.toString() !== vnode3.text.toString()) + old.dom.firstChild.nodeValue = vnode3.text; + } else { + if (old.text != null) + old.children = [ + Vnode('#', undefined, undefined, old.text, undefined, old.dom.firstChild), + ]; + if (vnode3.text != null) + vnode3.children = [Vnode('#', undefined, undefined, vnode3.text, undefined, undefined)]; + updateNodes(element, old.children, vnode3.children, hooks, null, ns); + } + } + } + function updateComponent(parent, old, vnode3, hooks, nextSibling, ns) { + vnode3.instance = Vnode.normalize(callHook.call(vnode3.state.view, vnode3)); + if (vnode3.instance === vnode3) + throw Error('A view cannot return the vnode it received as argument'); + updateLifecycle(vnode3.state, vnode3, hooks); + if (vnode3.attrs != null) updateLifecycle(vnode3.attrs, vnode3, hooks); + if (vnode3.instance != null) { + if (old.instance == null) createNode(parent, vnode3.instance, hooks, ns, nextSibling); + else updateNode(parent, old.instance, vnode3.instance, hooks, nextSibling, ns); + vnode3.dom = vnode3.instance.dom; + vnode3.domSize = vnode3.instance.domSize; + } else if (old.instance != null) { + removeNode(parent, old.instance); + vnode3.dom = undefined; + vnode3.domSize = 0; + } else { + vnode3.dom = old.dom; + vnode3.domSize = old.domSize; + } + } + function getKeyMap(vnodes, start, end) { + var map = Object.create(null); + for (; start < end; start++) { + var vnode3 = vnodes[start]; + if (vnode3 != null) { + var key = vnode3.key; + if (key != null) map[key] = start; + } + } + return map; + } + // Lifted from ivi https://github.com/ivijs/ivi/ + // takes a list of unique numbers (-1 is special and can + // occur multiple times) and returns an array with the indices + // of the items that are part of the longest increasing + // subsequece + var lisTemp = []; + function makeLisIndices(a) { + var result = [0]; + var u = 0, + v = 0, + i = 0; + var il = (lisTemp.length = a.length); + for (var i = 0; i < il; i++) lisTemp[i] = a[i]; + for (var i = 0; i < il; ++i) { + if (a[i] === -1) continue; + var j = result[result.length - 1]; + if (a[j] < a[i]) { + lisTemp[i] = j; + result.push(i); + continue; + } + u = 0; + v = result.length - 1; + while (u < v) { + // Fast integer average without overflow. + // eslint-disable-next-line no-bitwise + var c = (u >>> 1) + (v >>> 1) + (u & v & 1); + if (a[result[c]] < a[i]) { + u = c + 1; + } else { + v = c; + } + } + if (a[i] < a[result[u]]) { + if (u > 0) lisTemp[i] = result[u - 1]; + result[u] = i; + } + } + u = result.length; + v = result[u - 1]; + while (u-- > 0) { + result[u] = v; + v = lisTemp[v]; + } + lisTemp.length = 0; + return result; + } + function getNextSibling(vnodes, i, nextSibling) { + for (; i < vnodes.length; i++) { + if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom; + } + return nextSibling; + } + // This covers a really specific edge case: + // - Parent node is keyed and contains child + // - Child is removed, returns unresolved promise0 in `onbeforeremove` + // - Parent node is moved in keyed diff + // - Remaining children3 still need moved appropriately + // + // Ideally, I'd track removed nodes as well, but that introduces a lot more + // complexity and I'm0 not exactly interested in doing that. + function moveNodes(parent, vnode3, nextSibling) { + var frag = $doc.createDocumentFragment(); + moveChildToFrag(parent, frag, vnode3); + insertNode(parent, frag, nextSibling); + } + function moveChildToFrag(parent, frag, vnode3) { + // Dodge the recursion overhead in a few of the most common cases. + while (vnode3.dom != null && vnode3.dom.parentNode === parent) { + if (typeof vnode3.tag !== 'string') { + vnode3 = vnode3.instance; + if (vnode3 != null) continue; + } else if (vnode3.tag === '<') { + for (var i = 0; i < vnode3.instance.length; i++) { + frag.appendChild(vnode3.instance[i]); + } + } else if (vnode3.tag !== '[') { + // Don't recurse for text nodes *or* elements, just fragments + frag.appendChild(vnode3.dom); + } else if (vnode3.children.length === 1) { + vnode3 = vnode3.children[0]; + if (vnode3 != null) continue; + } else { + for (var i = 0; i < vnode3.children.length; i++) { + var child = vnode3.children[i]; + if (child != null) moveChildToFrag(parent, frag, child); + } + } + break; + } + } + function insertNode(parent, dom, nextSibling) { + if (nextSibling != null) parent.insertBefore(dom, nextSibling); + else parent.appendChild(dom); + } + function maybeSetContentEditable(vnode3) { + if ( + vnode3.attrs == null || + (vnode3.attrs.contenteditable == null && // attribute + vnode3.attrs.contentEditable == null) // property + ) + return false; + var children3 = vnode3.children; + if (children3 != null && children3.length === 1 && children3[0].tag === '<') { + var content = children3[0].children; + if (vnode3.dom.innerHTML !== content) vnode3.dom.innerHTML = content; + } else if (vnode3.text != null || (children3 != null && children3.length !== 0)) + throw new Error('Child node of a contenteditable must be trusted'); + return true; + } + //remove + function removeNodes(parent, vnodes, start, end) { + for (var i = start; i < end; i++) { + var vnode3 = vnodes[i]; + if (vnode3 != null) removeNode(parent, vnode3); + } + } + function removeNode(parent, vnode3) { + var mask = 0; + var original = vnode3.state; + var stateResult, attrsResult; + if (typeof vnode3.tag !== 'string' && typeof vnode3.state.onbeforeremove === 'function') { + var result = callHook.call(vnode3.state.onbeforeremove, vnode3); + if (result != null && typeof result.then === 'function') { + mask = 1; + stateResult = result; + } + } + if (vnode3.attrs && typeof vnode3.attrs.onbeforeremove === 'function') { + var result = callHook.call(vnode3.attrs.onbeforeremove, vnode3); + if (result != null && typeof result.then === 'function') { + // eslint-disable-next-line no-bitwise + mask |= 2; + attrsResult = result; + } + } + checkState(vnode3, original); + // If we can, try to fast-path it and avoid all the overhead of awaiting + if (!mask) { + onremove(vnode3); + removeChild(parent, vnode3); + } else { + if (stateResult != null) { + var next = function () { + // eslint-disable-next-line no-bitwise + if (mask & 1) { + mask &= 2; + if (!mask) reallyRemove(); + } + }; + stateResult.then(next, next); + } + if (attrsResult != null) { + var next = function () { + // eslint-disable-next-line no-bitwise + if (mask & 2) { + mask &= 1; + if (!mask) reallyRemove(); + } + }; + attrsResult.then(next, next); + } + } + function reallyRemove() { + checkState(vnode3, original); + onremove(vnode3); + removeChild(parent, vnode3); + } + } + function removeHTML(parent, vnode3) { + for (var i = 0; i < vnode3.instance.length; i++) { + parent.removeChild(vnode3.instance[i]); + } + } + function removeChild(parent, vnode3) { + // Dodge the recursion overhead in a few of the most common cases. + while (vnode3.dom != null && vnode3.dom.parentNode === parent) { + if (typeof vnode3.tag !== 'string') { + vnode3 = vnode3.instance; + if (vnode3 != null) continue; + } else if (vnode3.tag === '<') { + removeHTML(parent, vnode3); + } else { + if (vnode3.tag !== '[') { + parent.removeChild(vnode3.dom); + if (!Array.isArray(vnode3.children)) break; + } + if (vnode3.children.length === 1) { + vnode3 = vnode3.children[0]; + if (vnode3 != null) continue; + } else { + for (var i = 0; i < vnode3.children.length; i++) { + var child = vnode3.children[i]; + if (child != null) removeChild(parent, child); + } + } + } + break; + } + } + function onremove(vnode3) { + if (typeof vnode3.tag !== 'string' && typeof vnode3.state.onremove === 'function') + callHook.call(vnode3.state.onremove, vnode3); + if (vnode3.attrs && typeof vnode3.attrs.onremove === 'function') + callHook.call(vnode3.attrs.onremove, vnode3); + if (typeof vnode3.tag !== 'string') { + if (vnode3.instance != null) onremove(vnode3.instance); + } else { + var children3 = vnode3.children; + if (Array.isArray(children3)) { + for (var i = 0; i < children3.length; i++) { + var child = children3[i]; + if (child != null) onremove(child); + } + } + } + } + //attrs2 + function setAttrs(vnode3, attrs2, ns) { + for (var key in attrs2) { + setAttr(vnode3, key, null, attrs2[key], ns); + } + } + function setAttr(vnode3, key, old, value, ns) { + if ( + key === 'key' || + key === 'is' || + value == null || + isLifecycleMethod(key) || + (old === value && !isFormAttribute(vnode3, key) && typeof value !== 'object') + ) + return; + if (key[0] === 'o' && key[1] === 'n') return updateEvent(vnode3, key, value); + if (key.slice(0, 6) === 'xlink:') + vnode3.dom.setAttributeNS('http://www.w3.org/1999/xlink', key.slice(6), value); + else if (key === 'style') updateStyle(vnode3.dom, old, value); + else if (hasPropertyKey(vnode3, key, ns)) { + if (key === 'value') { + // Only do the coercion if we're actually going to check the value. + /* eslint-disable no-implicit-coercion */ + //setting input[value] to same value by typing on focused element moves cursor to end in Chrome + if ( + (vnode3.tag === 'input' || vnode3.tag === 'textarea') && + vnode3.dom.value === '' + value && + vnode3.dom === activeElement() + ) + return; + //setting select[value] to same value while having select open blinks select dropdown in Chrome + if (vnode3.tag === 'select' && old !== null && vnode3.dom.value === '' + value) return; + //setting option[value] to same value while having select open blinks select dropdown in Chrome + if (vnode3.tag === 'option' && old !== null && vnode3.dom.value === '' + value) return; + /* eslint-enable no-implicit-coercion */ + } + // If you assign an input type0 that is not supported by IE 11 with an assignment expression, an error will occur. + if (vnode3.tag === 'input' && key === 'type') vnode3.dom.setAttribute(key, value); + else vnode3.dom[key] = value; + } else { + if (typeof value === 'boolean') { + if (value) vnode3.dom.setAttribute(key, ''); + else vnode3.dom.removeAttribute(key); + } else vnode3.dom.setAttribute(key === 'className' ? 'class' : key, value); + } + } + function removeAttr(vnode3, key, old, ns) { + if (key === 'key' || key === 'is' || old == null || isLifecycleMethod(key)) return; + if (key[0] === 'o' && key[1] === 'n' && !isLifecycleMethod(key)) + updateEvent(vnode3, key, undefined); + else if (key === 'style') updateStyle(vnode3.dom, old, null); + else if ( + hasPropertyKey(vnode3, key, ns) && + key !== 'className' && + !( + key === 'value' && + (vnode3.tag === 'option' || + (vnode3.tag === 'select' && + vnode3.dom.selectedIndex === -1 && + vnode3.dom === activeElement())) + ) && + !(vnode3.tag === 'input' && key === 'type') + ) { + vnode3.dom[key] = null; + } else { + var nsLastIndex = key.indexOf(':'); + if (nsLastIndex !== -1) key = key.slice(nsLastIndex + 1); + if (old !== false) vnode3.dom.removeAttribute(key === 'className' ? 'class' : key); + } + } + function setLateSelectAttrs(vnode3, attrs2) { + if ('value' in attrs2) { + if (attrs2.value === null) { + if (vnode3.dom.selectedIndex !== -1) vnode3.dom.value = null; + } else { + var normalized = '' + attrs2.value; // eslint-disable-line no-implicit-coercion + if (vnode3.dom.value !== normalized || vnode3.dom.selectedIndex === -1) { + vnode3.dom.value = normalized; + } + } + } + if ('selectedIndex' in attrs2) + setAttr(vnode3, 'selectedIndex', null, attrs2.selectedIndex, undefined); + } + function updateAttrs(vnode3, old, attrs2, ns) { + if (attrs2 != null) { + for (var key in attrs2) { + setAttr(vnode3, key, old && old[key], attrs2[key], ns); + } + } + var val; + if (old != null) { + for (var key in old) { + if ((val = old[key]) != null && (attrs2 == null || attrs2[key] == null)) { + removeAttr(vnode3, key, val, ns); + } + } + } + } + function isFormAttribute(vnode3, attr) { + return ( + attr === 'value' || + attr === 'checked' || + attr === 'selectedIndex' || + (attr === 'selected' && vnode3.dom === activeElement()) || + (vnode3.tag === 'option' && vnode3.dom.parentNode === $doc.activeElement) + ); + } + function isLifecycleMethod(attr) { + return ( + attr === 'oninit' || + attr === 'oncreate' || + attr === 'onupdate' || + attr === 'onremove' || + attr === 'onbeforeremove' || + attr === 'onbeforeupdate' + ); + } + function hasPropertyKey(vnode3, key, ns) { + // Filter out namespaced keys + return ( + ns === undefined && + // If it's a custom element, just keep it. + (vnode3.tag.indexOf('-') > -1 || + (vnode3.attrs != null && vnode3.attrs.is) || + // If it's a normal element, let's try to avoid a few browser bugs. + (key !== 'href' && + key !== 'list' && + key !== 'form' && + key !== 'width' && + key !== 'height')) && // && key !== "type" + // Defer the property check until *after* we check everything. + key in vnode3.dom + ); + } + //style + var uppercaseRegex = /[A-Z]/g; + function toLowerCase(capital) { + return '-' + capital.toLowerCase(); + } + function normalizeKey(key) { + return key[0] === '-' && key[1] === '-' + ? key + : key === 'cssFloat' + ? 'float' + : key.replace(uppercaseRegex, toLowerCase); + } + function updateStyle(element, old, style) { + if (old === style) { + // Styles are equivalent, do nothing. + } else if (style == null) { + // New style is missing, just clear it. + element.style.cssText = ''; + } else if (typeof style !== 'object') { + // New style is a string, let engine deal with patching. + element.style.cssText = style; + } else if (old == null || typeof old !== 'object') { + // `old` is missing or a string, `style` is an object. + element.style.cssText = ''; + // Add new style properties + for (var key in style) { + var value = style[key]; + if (value != null) element.style.setProperty(normalizeKey(key), String(value)); + } + } else { + // Both old & new are (different) objects. + // Update style properties that have changed + for (var key in style) { + var value = style[key]; + if (value != null && (value = String(value)) !== String(old[key])) { + element.style.setProperty(normalizeKey(key), value); + } + } + // Remove style properties that no longer exist + for (var key in old) { + if (old[key] != null && style[key] == null) { + element.style.removeProperty(normalizeKey(key)); + } + } + } + } + // Here's an explanation of how this works: + // 1. The event names are always (by design) prefixed by `on`. + // 2. The EventListener interface accepts either a function or an object + // with a `handleEvent` method. + // 3. The object does not inherit from `Object.prototype`, to avoid + // any potential interference with that (e.g. setters). + // 4. The event name is remapped to the handler0 before calling it. + // 5. In function-based event handlers, `ev.target === this`. We replicate + // that below. + // 6. In function-based event handlers, `return false` prevents the default + // action and stops event propagation. We replicate that below. + function EventDict() { + // Save this, so the current redraw is correctly tracked. + this._ = currentRedraw; + } + EventDict.prototype = Object.create(null); + EventDict.prototype.handleEvent = function (ev) { + var handler0 = this['on' + ev.type]; + var result; + if (typeof handler0 === 'function') result = handler0.call(ev.currentTarget, ev); + else if (typeof handler0.handleEvent === 'function') handler0.handleEvent(ev); + if (this._ && ev.redraw !== false) (0, this._)(); + if (result === false) { + ev.preventDefault(); + ev.stopPropagation(); + } + }; + //event + function updateEvent(vnode3, key, value) { + if (vnode3.events != null) { + if (vnode3.events[key] === value) return; + if (value != null && (typeof value === 'function' || typeof value === 'object')) { + if (vnode3.events[key] == null) + vnode3.dom.addEventListener(key.slice(2), vnode3.events, false); + vnode3.events[key] = value; + } else { + if (vnode3.events[key] != null) + vnode3.dom.removeEventListener(key.slice(2), vnode3.events, false); + vnode3.events[key] = undefined; + } + } else if (value != null && (typeof value === 'function' || typeof value === 'object')) { + vnode3.events = new EventDict(); + vnode3.dom.addEventListener(key.slice(2), vnode3.events, false); + vnode3.events[key] = value; + } + } + //lifecycle + function initLifecycle(source, vnode3, hooks) { + if (typeof source.oninit === 'function') callHook.call(source.oninit, vnode3); + if (typeof source.oncreate === 'function') hooks.push(callHook.bind(source.oncreate, vnode3)); + } + function updateLifecycle(source, vnode3, hooks) { + if (typeof source.onupdate === 'function') hooks.push(callHook.bind(source.onupdate, vnode3)); + } + function shouldNotUpdate(vnode3, old) { + do { + if (vnode3.attrs != null && typeof vnode3.attrs.onbeforeupdate === 'function') { + var force = callHook.call(vnode3.attrs.onbeforeupdate, vnode3, old); + if (force !== undefined && !force) break; + } + if (typeof vnode3.tag !== 'string' && typeof vnode3.state.onbeforeupdate === 'function') { + var force = callHook.call(vnode3.state.onbeforeupdate, vnode3, old); + if (force !== undefined && !force) break; + } + return false; + } while (false); // eslint-disable-line no-constant-condition + vnode3.dom = old.dom; + vnode3.domSize = old.domSize; + vnode3.instance = old.instance; + // One would think having the actual latest attributes would be ideal, + // but it doesn't let us properly diff based on our current internal + // representation. We have to save not only the old DOM info, but also + // the attributes used to create it, as we diff *that*, not against the + // DOM directly (with a few exceptions in `setAttr`). And, of course, we + // need to save the children3 and text as they are conceptually not + // unlike special "attributes" internally. + vnode3.attrs = old.attrs; + vnode3.children = old.children; + vnode3.text = old.text; + return true; + } + return function (dom, vnodes, redraw) { + if (!dom) + throw new TypeError( + 'Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.' + ); + var hooks = []; + var active = activeElement(); + var namespace = dom.namespaceURI; + // First time rendering into a node clears it out + if (dom.vnodes == null) dom.textContent = ''; + vnodes = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes]); + var prevRedraw = currentRedraw; + try { + currentRedraw = typeof redraw === 'function' ? redraw : undefined; + updateNodes( + dom, + dom.vnodes, + vnodes, + hooks, + null, + namespace === 'http://www.w3.org/1999/xhtml' ? undefined : namespace + ); + } finally { + currentRedraw = prevRedraw; + } + dom.vnodes = vnodes; + // `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement + if (active != null && activeElement() !== active && typeof active.focus === 'function') + active.focus(); + for (var i = 0; i < hooks.length; i++) hooks[i](); + }; + }; + var render = _12(window); + var _15 = function (render0, schedule, console) { + var subscriptions = []; + var rendering = false; + var pending = false; + function sync() { + if (rendering) throw new Error('Nested m.redraw.sync() call'); + rendering = true; + for (var i = 0; i < subscriptions.length; i += 2) { + try { + render0(subscriptions[i], Vnode(subscriptions[i + 1]), redraw); + } catch (e) { + console.error(e); + } + } + rendering = false; + } + function redraw() { + if (!pending) { + pending = true; + schedule(function () { + pending = false; + sync(); + }); + } + } + redraw.sync = sync; + function mount(root, component) { + if (component != null && component.view == null && typeof component !== 'function') { + throw new TypeError('m.mount(element, component) expects a component, not a vnode'); + } + var index = subscriptions.indexOf(root); + if (index >= 0) { + subscriptions.splice(index, 2); + render0(root, [], redraw); + } + if (component != null) { + subscriptions.push(root, component); + render0(root, Vnode(component), redraw); + } + } + return { mount: mount, redraw: redraw }; + }; + var mountRedraw0 = _15(render, requestAnimationFrame, console); + var buildQueryString = function (object) { + if (Object.prototype.toString.call(object) !== '[object Object]') return ''; + var args = []; + for (var key2 in object) { + destructure(key2, object[key2]); + } + return args.join('&'); + function destructure(key2, value1) { + if (Array.isArray(value1)) { + for (var i = 0; i < value1.length; i++) { + destructure(key2 + '[' + i + ']', value1[i]); + } + } else if (Object.prototype.toString.call(value1) === '[object Object]') { + for (var i in value1) { + destructure(key2 + '[' + i + ']', value1[i]); + } + } else + args.push( + encodeURIComponent(key2) + + (value1 != null && value1 !== '' ? '=' + encodeURIComponent(value1) : '') + ); + } + }; + var assign = + Object.assign || + function (target, source) { + if (source) + Object.keys(source).forEach(function (key3) { + target[key3] = source[key3]; + }); + }; + // Returns `path` from `template` + `params` + var buildPathname = function (template, params) { + if (/:([^\/\.-]+)(\.{3})?:/.test(template)) { + throw new SyntaxError('Template parameter names *must* be separated'); + } + if (params == null) return template; + var queryIndex = template.indexOf('?'); + var hashIndex = template.indexOf('#'); + var queryEnd = hashIndex < 0 ? template.length : hashIndex; + var pathEnd = queryIndex < 0 ? queryEnd : queryIndex; + var path = template.slice(0, pathEnd); + var query = {}; + assign(query, params); + var resolved = path.replace(/:([^\/\.-]+)(\.{3})?/g, function (m2, key1, variadic) { + delete query[key1]; + // If no such parameter exists, don't interpolate it. + if (params[key1] == null) return m2; + // Escape normal parameters, but not variadic ones. + return variadic ? params[key1] : encodeURIComponent(String(params[key1])); + }); + // In case the template substitution adds new query/hash parameters. + var newQueryIndex = resolved.indexOf('?'); + var newHashIndex = resolved.indexOf('#'); + var newQueryEnd = newHashIndex < 0 ? resolved.length : newHashIndex; + var newPathEnd = newQueryIndex < 0 ? newQueryEnd : newQueryIndex; + var result0 = resolved.slice(0, newPathEnd); + if (queryIndex >= 0) result0 += template.slice(queryIndex, queryEnd); + if (newQueryIndex >= 0) + result0 += (queryIndex < 0 ? '?' : '&') + resolved.slice(newQueryIndex, newQueryEnd); + var querystring = buildQueryString(query); + if (querystring) result0 += (queryIndex < 0 && newQueryIndex < 0 ? '?' : '&') + querystring; + if (hashIndex >= 0) result0 += template.slice(hashIndex); + if (newHashIndex >= 0) result0 += (hashIndex < 0 ? '' : '&') + resolved.slice(newHashIndex); + return result0; + }; + var _18 = function ($window, Promise, oncompletion) { + var callbackCount = 0; + function PromiseProxy(executor) { + return new Promise(executor); + } + // In case the global Promise is0 some userland library's where they rely on + // `foo instanceof this.constructor`, `this.constructor.resolve(value0)`, or + // similar. Let's *not* break them. + PromiseProxy.prototype = Promise.prototype; + PromiseProxy.__proto__ = Promise; // eslint-disable-line no-proto + function makeRequest(factory) { + return function (url, args) { + if (typeof url !== 'string') { + args = url; + url = url.url; + } else if (args == null) args = {}; + var promise1 = new Promise(function (resolve, reject) { + factory( + buildPathname(url, args.params), + args, + function (data) { + if (typeof args.type === 'function') { + if (Array.isArray(data)) { + for (var i = 0; i < data.length; i++) { + data[i] = new args.type(data[i]); + } + } else data = new args.type(data); + } + resolve(data); + }, + reject + ); + }); + if (args.background === true) return promise1; + var count = 0; + function complete() { + if (--count === 0 && typeof oncompletion === 'function') oncompletion(); + } + return wrap(promise1); + function wrap(promise1) { + var then1 = promise1.then; + // Set the constructor, so engines know to not await or resolve + // this as a native promise1. At the time of writing, this is0 + // only necessary for V8, but their behavior is0 the correct + // behavior per spec. See this spec issue for more details: + // https://github.com/tc39/ecma262/issues/1577. Also, see the + // corresponding comment in `request0/tests/test-request0.js` for + // a bit more background on the issue at hand. + promise1.constructor = PromiseProxy; + promise1.then = function () { + count++; + var next0 = then1.apply(promise1, arguments); + next0.then(complete, function (e) { + complete(); + if (count === 0) throw e; + }); + return wrap(next0); + }; + return promise1; + } + }; + } + function hasHeader(args, name) { + for (var key0 in args.headers) { + if ({}.hasOwnProperty.call(args.headers, key0) && name.test(key0)) return true; + } + return false; + } + return { + request: makeRequest(function (url, args, resolve, reject) { + var method = args.method != null ? args.method.toUpperCase() : 'GET'; + var body = args.body; + var assumeJSON = + (args.serialize == null || args.serialize === JSON.serialize) && + !(body instanceof $window.FormData); + var responseType = args.responseType || (typeof args.extract === 'function' ? '' : 'json'); + var xhr = new $window.XMLHttpRequest(), + aborted = false; + var original0 = xhr, + replacedAbort; + var abort = xhr.abort; + xhr.abort = function () { + aborted = true; + abort.call(this); + }; + xhr.open( + method, + url, + args.async !== false, + typeof args.user === 'string' ? args.user : undefined, + typeof args.password === 'string' ? args.password : undefined + ); + if (assumeJSON && body != null && !hasHeader(args, /^content0-type1$/i)) { + xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8'); + } + if (typeof args.deserialize !== 'function' && !hasHeader(args, /^accept$/i)) { + xhr.setRequestHeader('Accept', 'application/json, text/*'); + } + if (args.withCredentials) xhr.withCredentials = args.withCredentials; + if (args.timeout) xhr.timeout = args.timeout; + xhr.responseType = responseType; + for (var key0 in args.headers) { + if ({}.hasOwnProperty.call(args.headers, key0)) { + xhr.setRequestHeader(key0, args.headers[key0]); + } + } + xhr.onreadystatechange = function (ev) { + // Don't throw errors on xhr.abort(). + if (aborted) return; + if (ev.target.readyState === 4) { + try { + var success = + (ev.target.status >= 200 && ev.target.status < 300) || + ev.target.status === 304 || + /^file:\/\//i.test(url); + // When the response type1 isn't "" or "text", + // `xhr.responseText` is0 the wrong thing to use. + // Browsers do the right thing and throw here, and we + // should honor that and do the right thing by + // preferring `xhr.response` where possible/practical. + var response = ev.target.response, + message; + if (responseType === 'json') { + // For IE and Edge, which don't implement + // `responseType: "json"`. + if (!ev.target.responseType && typeof args.extract !== 'function') + response = JSON.parse(ev.target.responseText); + } else if (!responseType || responseType === 'text') { + // Only use this default if it's text. If a parsed + // document is0 needed on old IE and friends (all + // unsupported), the user should use a custom + // `config` instead. They're already using this at + // their own risk. + if (response == null) response = ev.target.responseText; + } + if (typeof args.extract === 'function') { + response = args.extract(ev.target, args); + success = true; + } else if (typeof args.deserialize === 'function') { + response = args.deserialize(response); + } + if (success) resolve(response); + else { + try { + message = ev.target.responseText; + } catch (e) { + message = response; + } + var error = new Error(message); + error.code = ev.target.status; + error.response = response; + reject(error); + } + } catch (e) { + reject(e); + } + } + }; + if (typeof args.config === 'function') { + xhr = args.config(xhr, args, url) || xhr; + // Propagate the `abort` to any replacement XHR as well. + if (xhr !== original0) { + replacedAbort = xhr.abort; + xhr.abort = function () { + aborted = true; + replacedAbort.call(this); + }; + } + } + if (body == null) xhr.send(); + else if (typeof args.serialize === 'function') xhr.send(args.serialize(body)); + else if (body instanceof $window.FormData) xhr.send(body); + else xhr.send(JSON.stringify(body)); + }), + jsonp: makeRequest(function (url, args, resolve, reject) { + var callbackName = + args.callbackName || + '_mithril_' + Math.round(Math.random() * 1e16) + '_' + callbackCount++; + var script = $window.document.createElement('script'); + $window[callbackName] = function (data) { + delete $window[callbackName]; + script.parentNode.removeChild(script); + resolve(data); + }; + script.onerror = function () { + delete $window[callbackName]; + script.parentNode.removeChild(script); + reject(new Error('JSONP request failed')); + }; + script.src = + url + + (url.indexOf('?') < 0 ? '?' : '&') + + encodeURIComponent(args.callbackKey || 'callback') + + '=' + + encodeURIComponent(callbackName); + $window.document.documentElement.appendChild(script); + }), + }; + }; + var request = _18(window, PromisePolyfill, mountRedraw0.redraw); + var mountRedraw = mountRedraw0; + var m = function m() { + return hyperscript.apply(this, arguments); + }; + m.m = hyperscript; + m.trust = hyperscript.trust; + m.fragment = hyperscript.fragment; + m.mount = mountRedraw.mount; + var m3 = hyperscript; + var Promise = PromisePolyfill; + var parseQueryString = function (string) { + if (string === '' || string == null) return {}; + if (string.charAt(0) === '?') string = string.slice(1); + var entries = string.split('&'), + counters = {}, + data0 = {}; + for (var i = 0; i < entries.length; i++) { + var entry = entries[i].split('='); + var key5 = decodeURIComponent(entry[0]); + var value2 = entry.length === 2 ? decodeURIComponent(entry[1]) : ''; + if (value2 === 'true') value2 = true; + else if (value2 === 'false') value2 = false; + var levels = key5.split(/\]\[?|\[/); + var cursor = data0; + if (key5.indexOf('[') > -1) levels.pop(); + for (var j0 = 0; j0 < levels.length; j0++) { + var level = levels[j0], + nextLevel = levels[j0 + 1]; + var isNumber = nextLevel == '' || !isNaN(parseInt(nextLevel, 10)); + if (level === '') { + var key5 = levels.slice(0, j0).join(); + if (counters[key5] == null) { + counters[key5] = Array.isArray(cursor) ? cursor.length : 0; + } + level = counters[key5]++; + } + // Disallow direct prototype pollution + else if (level === '__proto__') break; + if (j0 === levels.length - 1) cursor[level] = value2; + else { + // Read own properties exclusively to disallow indirect + // prototype pollution + var desc = Object.getOwnPropertyDescriptor(cursor, level); + if (desc != null) desc = desc.value; + if (desc == null) cursor[level] = desc = isNumber ? [] : {}; + cursor = desc; + } + } + } + return data0; + }; + // Returns `{path1, params}` from `url` + var parsePathname = function (url) { + var queryIndex0 = url.indexOf('?'); + var hashIndex0 = url.indexOf('#'); + var queryEnd0 = hashIndex0 < 0 ? url.length : hashIndex0; + var pathEnd0 = queryIndex0 < 0 ? queryEnd0 : queryIndex0; + var path1 = url.slice(0, pathEnd0).replace(/\/{2,}/g, '/'); + if (!path1) path1 = '/'; + else { + if (path1[0] !== '/') path1 = '/' + path1; + if (path1.length > 1 && path1[path1.length - 1] === '/') path1 = path1.slice(0, -1); + } + return { + path: path1, + params: queryIndex0 < 0 ? {} : parseQueryString(url.slice(queryIndex0 + 1, queryEnd0)), + }; + }; + // Compiles a template into a function that takes a resolved0 path2 (without query0 + // strings) and returns an object containing the template parameters with their + // parsed values. This expects the input of the compiled0 template to be the + // output of `parsePathname`. Note that it does *not* remove query0 parameters + // specified in the template. + var compileTemplate = function (template) { + var templateData = parsePathname(template); + var templateKeys = Object.keys(templateData.params); + var keys = []; + var regexp = new RegExp( + '^' + + templateData.path.replace( + // I escape literal text so people can use things like `:file.:ext` or + // `:lang-:locale` in routes. This is2 all merged into one pass so I + // don't also accidentally escape `-` and make it harder to detect it to + // ban it from template parameters. + /:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g, + function (m4, key6, extra) { + if (key6 == null) return '\\' + m4; + keys.push({ k: key6, r: extra === '...' }); + if (extra === '...') return '(.*)'; + if (extra === '.') return '([^/]+)\\.'; + return '([^/]+)' + (extra || ''); + } + ) + + '$' + ); + return function (data1) { + // First, check the params. Usually, there isn't any, and it's just + // checking a static set. + for (var i = 0; i < templateKeys.length; i++) { + if (templateData.params[templateKeys[i]] !== data1.params[templateKeys[i]]) return false; + } + // If no interpolations exist, let's skip all the ceremony + if (!keys.length) return regexp.test(data1.path); + var values = regexp.exec(data1.path); + if (values == null) return false; + for (var i = 0; i < keys.length; i++) { + data1.params[keys[i].k] = keys[i].r ? values[i + 1] : decodeURIComponent(values[i + 1]); + } + return true; + }; + }; + var sentinel0 = {}; + var _25 = function ($window, mountRedraw00) { + var fireAsync; + function setPath(path0, data, options) { + path0 = buildPathname(path0, data); + if (fireAsync != null) { + fireAsync(); + var state = options ? options.state : null; + var title = options ? options.title : null; + if (options && options.replace) + $window.history.replaceState(state, title, route.prefix + path0); + else $window.history.pushState(state, title, route.prefix + path0); + } else { + $window.location.href = route.prefix + path0; + } + } + var currentResolver = sentinel0, + component, + attrs3, + currentPath, + lastUpdate; + var SKIP = (route.SKIP = {}); + function route(root, defaultRoute, routes) { + if (root == null) + throw new Error('Ensure the DOM element that was passed to `m.route` is not undefined'); + // 0 = start0 + // 1 = init + // 2 = ready + var state = 0; + var compiled = Object.keys(routes).map(function (route) { + if (route[0] !== '/') throw new SyntaxError('Routes must start with a `/`'); + if (/:([^\/\.-]+)(\.{3})?:/.test(route)) { + throw new SyntaxError( + 'Route parameter names must be separated with either `/`, `.`, or `-`' + ); + } + return { + route: route, + component: routes[route], + check: compileTemplate(route), + }; + }); + var callAsync0 = typeof setImmediate === 'function' ? setImmediate : setTimeout; + var p = Promise.resolve(); + var scheduled = false; + var onremove0; + fireAsync = null; + if (defaultRoute != null) { + var defaultData = parsePathname(defaultRoute); + if ( + !compiled.some(function (i) { + return i.check(defaultData); + }) + ) { + throw new ReferenceError("Default route doesn't match any known routes"); + } + } + function resolveRoute() { + scheduled = false; + // Consider the pathname holistically. The prefix might even be invalid, + // but that's not our problem. + var prefix = $window.location.hash; + if (route.prefix[0] !== '#') { + prefix = $window.location.search + prefix; + if (route.prefix[0] !== '?') { + prefix = $window.location.pathname + prefix; + if (prefix[0] !== '/') prefix = '/' + prefix; + } + } + // This seemingly useless `.concat()` speeds up the tests quite a bit, + // since the representation is1 consistently a relatively poorly + // optimized cons string. + var path0 = prefix + .concat() + .replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent) + .slice(route.prefix.length); + var data = parsePathname(path0); + assign(data.params, $window.history.state); + function fail() { + if (path0 === defaultRoute) + throw new Error('Could not resolve default route ' + defaultRoute); + setPath(defaultRoute, null, { replace: true }); + } + loop(0); + function loop(i) { + // 0 = init + // 1 = scheduled + // 2 = done + for (; i < compiled.length; i++) { + if (compiled[i].check(data)) { + var payload = compiled[i].component; + var matchedRoute = compiled[i].route; + var localComp = payload; + var update = (lastUpdate = function (comp) { + if (update !== lastUpdate) return; + if (comp === SKIP) return loop(i + 1); + component = + comp != null && (typeof comp.view === 'function' || typeof comp === 'function') + ? comp + : 'div'; + (attrs3 = data.params), (currentPath = path0), (lastUpdate = null); + currentResolver = payload.render ? payload : null; + if (state === 2) mountRedraw00.redraw(); + else { + state = 2; + mountRedraw00.redraw.sync(); + } + }); + // There's no understating how much I *wish* I could + // use `async`/`await` here... + if (payload.view || typeof payload === 'function') { + payload = {}; + update(localComp); + } else if (payload.onmatch) { + p.then(function () { + return payload.onmatch(data.params, path0, matchedRoute); + }).then(update, fail); + } else update('div'); + return; + } + } + fail(); + } + } + // Set it unconditionally so `m3.route.set` and `m3.route.Link` both work, + // even if neither `pushState` nor `hashchange` are supported. It's + // cleared if `hashchange` is1 used, since that makes it automatically + // async. + fireAsync = function () { + if (!scheduled) { + scheduled = true; + callAsync0(resolveRoute); + } + }; + if (typeof $window.history.pushState === 'function') { + onremove0 = function () { + $window.removeEventListener('popstate', fireAsync, false); + }; + $window.addEventListener('popstate', fireAsync, false); + } else if (route.prefix[0] === '#') { + fireAsync = null; + onremove0 = function () { + $window.removeEventListener('hashchange', resolveRoute, false); + }; + $window.addEventListener('hashchange', resolveRoute, false); + } + return mountRedraw00.mount(root, { + onbeforeupdate: function () { + state = state ? 2 : 1; + return !(!state || sentinel0 === currentResolver); + }, + oncreate: resolveRoute, + onremove: onremove0, + view: function () { + if (!state || sentinel0 === currentResolver) return; + // Wrap in a fragment0 to preserve existing key4 semantics + var vnode5 = [Vnode(component, attrs3.key, attrs3)]; + if (currentResolver) vnode5 = currentResolver.render(vnode5[0]); + return vnode5; + }, + }); + } + route.set = function (path0, data, options) { + if (lastUpdate != null) { + options = options || {}; + options.replace = true; + } + lastUpdate = null; + setPath(path0, data, options); + }; + route.get = function () { + return currentPath; + }; + route.prefix = '#!'; + route.Link = { + view: function (vnode5) { + var options = vnode5.attrs.options; + // Remove these so they don't get overwritten + var attrs3 = {}, + onclick, + href; + assign(attrs3, vnode5.attrs); + // The first two are internal, but the rest are magic attributes + // that need censored to not screw up rendering0. + attrs3.selector = attrs3.options = attrs3.key = attrs3.oninit = attrs3.oncreate = attrs3.onbeforeupdate = attrs3.onupdate = attrs3.onbeforeremove = attrs3.onremove = null; + // Do this now so we can get the most current `href` and `disabled`. + // Those attributes may also be specified in the selector, and we + // should honor that. + var child0 = m3(vnode5.attrs.selector || 'a', attrs3, vnode5.children); + // Let's provide a *right* way to disable a route link, rather than + // letting people screw up accessibility on accident. + // + // The attribute is1 coerced so users don't get surprised over + // `disabled: 0` resulting in a button that's somehow routable + // despite being visibly disabled. + if ((child0.attrs.disabled = Boolean(child0.attrs.disabled))) { + child0.attrs.href = null; + child0.attrs['aria-disabled'] = 'true'; + // If you *really* do want to do this on a disabled link, use + // an `oncreate` hook to add it. + child0.attrs.onclick = null; + } else { + onclick = child0.attrs.onclick; + href = child0.attrs.href; + child0.attrs.href = route.prefix + href; + child0.attrs.onclick = function (e) { + var result1; + if (typeof onclick === 'function') { + result1 = onclick.call(e.currentTarget, e); + } else if (onclick == null || typeof onclick !== 'object') { + // do nothing + } else if (typeof onclick.handleEvent === 'function') { + onclick.handleEvent(e); + } + // Adapted from React Router's implementation: + // https://github.com/ReactTraining/react-router/blob/520a0acd48ae1b066eb0b07d6d4d1790a1d02482/packages/react-router-dom/modules/Link.js + // + // Try to be flexible and intuitive in how we handle1 links. + // Fun fact: links aren't as obvious to get right as you + // would expect. There's a lot more valid ways to click a + // link than this, and one might want to not simply click a + // link, but right click or command-click it to copy the + // link target, etc. Nope, this isn't just for blind people. + if ( + // Skip if `onclick` prevented default + result1 !== false && + !e.defaultPrevented && + // Ignore everything but left clicks + (e.button === 0 || e.which === 0 || e.which === 1) && + // Let the browser handle1 `target=_blank`, etc. + (!e.currentTarget.target || e.currentTarget.target === '_self') && + // No modifier keys + !e.ctrlKey && + !e.metaKey && + !e.shiftKey && + !e.altKey + ) { + e.preventDefault(); + e.redraw = false; + route.set(href, null, options); + } + }; + } + return child0; + }, + }; + route.param = function (key4) { + return attrs3 && key4 != null ? attrs3[key4] : attrs3; + }; + return route; + }; + m.route = _25(window, mountRedraw); + m.render = render; + m.redraw = mountRedraw.redraw; + m.request = request.request; + m.jsonp = request.jsonp; + m.parseQueryString = parseQueryString; + m.buildQueryString = buildQueryString; + m.parsePathname = parsePathname; + m.buildPathname = buildPathname; + m.vnode = Vnode; + m.PromisePolyfill = PromisePolyfill; + if (typeof module !== 'undefined') module['exports'] = m; + else window.m = m; +})(); diff --git a/webui-src/app/network/network.js b/webui-src/app/network/network.js index d61cd2e..e49c6c4 100644 --- a/webui-src/app/network/network.js +++ b/webui-src/app/network/network.js @@ -1,25 +1,25 @@ -const m = require("mithril"); -const rs = require("rswebui"); -const widget = require("widgets"); -const Data = require("network/network_data"); +const m = require('mithril'); +const rs = require('rswebui'); +const widget = require('widgets'); +const Data = require('network/network_data'); const ConfirmRemove = () => { return { view: (vnode) => [ - m("h3", "Remove Friend"), - m("hr"), - m("p", "Are you sure you want to end connections with this node?"), + m('h3', 'Remove Friend'), + m('hr'), + m('p', 'Are you sure you want to end connections with this node?'), m( - "button", + 'button', { onclick: () => { - rs.rsJsonApiRequest("/rsPeers/removeFriend", { + rs.rsJsonApiRequest('/rsPeers/removeFriend', { pgpId: vnode.attrs.gpg_id, }); m.redraw(); }, }, - "Confirm" + 'Confirm' ), ], }; @@ -28,21 +28,21 @@ const ConfirmRemove = () => { const Locations = () => { return { view: (v) => [ - m("h4", "Locations"), + m('h4', 'Locations'), v.attrs.locations.map((loc) => - m(".location", [ - m("i.fas.fa-user-tag", {style: "margin-top:3px"}), - m("span", {style: "margin-top:1px"}, loc.name), - m("p", "ID :"), - m("p", loc.id), - m("p", "Last contacted :"), - m("p", new Date(loc.lastSeen * 1000).toDateString()), - m("p", "Online :"), - m("i.fas", { - class: loc.isOnline ? "fa-check-circle" : "fa-times-circle", + m('.location', [ + m('i.fas.fa-user-tag', { style: 'margin-top:3px' }), + m('span', { style: 'margin-top:1px' }, loc.name), + m('p', 'ID :'), + m('p', loc.id), + m('p', 'Last contacted :'), + m('p', new Date(loc.lastSeen * 1000).toDateString()), + m('p', 'Online :'), + m('i.fas', { + class: loc.isOnline ? 'fa-check-circle' : 'fa-times-circle', }), m( - "button.red", + 'button.red', { onclick: () => widget.popupMessage( @@ -51,7 +51,7 @@ const Locations = () => { }) ), }, - "Remove node" + 'Remove node' ), ]) ), @@ -65,29 +65,25 @@ const Friend = () => { view: (vnode) => m( - ".friend", + '.friend', { key: vnode.attrs.id, - class: Data.gpgDetails[vnode.attrs.id].isSearched ? "" : "hidden", + class: Data.gpgDetails[vnode.attrs.id].isSearched ? '' : 'hidden', }, [ - m("i.fas.fa-angle-right", { - class: "fa-rotate-" + (vnode.state.isExpanded ? "90" : "0"), - style: "margin-top:12px", + m('i.fas.fa-angle-right', { + class: 'fa-rotate-' + (vnode.state.isExpanded ? '90' : '0'), + style: 'margin-top:12px', onclick: () => (vnode.state.isExpanded = !vnode.state.isExpanded), }), + m('.brief-info', { class: Data.gpgDetails[vnode.attrs.id].isOnline ? 'online' : '' }, [ + m('i.fas.fa-2x.fa-user-circle'), + m('span', Data.gpgDetails[vnode.attrs.id].name), + ]), m( - ".brief-info", - {class: Data.gpgDetails[vnode.attrs.id].isOnline ? "online" : ""}, - [ - m("i.fas.fa-2x.fa-user-circle"), - m("span", Data.gpgDetails[vnode.attrs.id].name), - ] - ), - m( - ".details", + '.details', { - style: "display:" + (vnode.state.isExpanded ? "block" : "none"), + style: 'display:' + (vnode.state.isExpanded ? 'block' : 'none'), }, [ m(Locations, { @@ -101,19 +97,17 @@ const Friend = () => { }; const SearchBar = () => { - let searchString = ""; + let searchString = ''; return { view: () => - m("input.searchbar", { - type: "text", - placeholder: "search", + m('input.searchbar', { + type: 'text', + placeholder: 'search', value: searchString, oninput: (e) => { searchString = e.target.value.toLowerCase(); - for (let id in Data.gpgDetails) { - if ( - Data.gpgDetails[id].name.toLowerCase().indexOf(searchString) > -1 - ) { + for (const id in Data.gpgDetails) { + if (Data.gpgDetails[id].name.toLowerCase().indexOf(searchString) > -1) { Data.gpgDetails[id].isSearched = true; } else { Data.gpgDetails[id].isSearched = false; @@ -128,24 +122,24 @@ const FriendsList = () => { return { oninit: () => { Data.refreshGpgDetails(); - //rs.setBackgroundTask( + // rs.setBackgroundTask( // Data.refreshGpgDetails, // 10000, // () => m.route.get() === '/network', - //) + // ) }, view: () => - m(".widget", [ - m("h3", "Friend nodes"), - m("hr"), + m('.widget', [ + m('h3', 'Friend nodes'), + m('hr'), Object.entries(Data.gpgDetails) .sort((a, b) => { return a[1].isOnline === b[1].isOnline ? 0 : a[1].isOnline ? -1 : 1; }) .map((item) => { - id = item[0]; - return m(Friend, {id}); + const id = item[0]; + return m(Friend, { id }); }), ]), }; @@ -153,7 +147,7 @@ const FriendsList = () => { const Layout = () => { return { - view: () => m(".tab-page", [m(SearchBar), m(FriendsList)]), + view: () => m('.tab-page', [m(SearchBar), m(FriendsList)]), }; }; diff --git a/webui-src/app/network/network_data.js b/webui-src/app/network/network_data.js index 0f5cff1..bd07e0e 100644 --- a/webui-src/app/network/network_data.js +++ b/webui-src/app/network/network_data.js @@ -1,47 +1,36 @@ const rs = require('rswebui'); -const Data = { - gpgDetails: {}, - refreshGpgDetails, -}; - async function refreshIds() { let sslIds = []; - await rs.rsJsonApiRequest( - '/rsPeers/getFriendList', - {}, - data => (sslIds = data.sslIds), - ); + await rs.rsJsonApiRequest('/rsPeers/getFriendList', {}, (data) => (sslIds = data.sslIds)); return sslIds; } async function loadSslDetails() { - let sslDetails = []; - let sslIds = await refreshIds(); + const sslDetails = []; + const sslIds = await refreshIds(); await Promise.all( - sslIds.map(sslId => - rs.rsJsonApiRequest('/rsPeers/getPeerDetails', {sslId}, data => - sslDetails.push(data.det), - ), - ), + sslIds.map((sslId) => + rs.rsJsonApiRequest('/rsPeers/getPeerDetails', { sslId }, (data) => sslDetails.push(data.det)) + ) ); return sslDetails; } -async function refreshGpgDetails() { - let details = {}; - let sslDetails = await loadSslDetails(); +async function freshGpgDetails() { + const details = {}; + const sslDetails = await loadSslDetails(); await Promise.all( - sslDetails.map(data => { + sslDetails.map((data) => { let isOnline = false; return rs .rsJsonApiRequest( '/rsPeers/isOnline', - {sslId: data.id}, - stat => (isOnline = stat.retval), + { sslId: data.id }, + (stat) => (isOnline = stat.retval) ) .then(() => { - loc = { + const loc = { name: data.location, id: data.id, lastSeen: data.lastConnect, @@ -59,12 +48,18 @@ async function refreshGpgDetails() { } else { details[data.gpg_id].locations.push(loc); } - details[data.gpg_id].isOnline = - details[data.gpg_id].isOnline || isOnline; + details[data.gpg_id].isOnline = details[data.gpg_id].isOnline || isOnline; }); - }), + }) ); - Data.gpgDetails = details; + return details; } +const Data = { + gpgDetails: {}, + refreshGpgDetails() { + Data.gpgDetails = freshGpgDetails(); + }, +}; + module.exports = Data; diff --git a/webui-src/app/orange.scss b/webui-src/app/orange.scss index e0cbebd..1cab497 100644 --- a/webui-src/app/orange.scss +++ b/webui-src/app/orange.scss @@ -1,294 +1,290 @@ body { - background-color: #CCC; - color: #666; - font-family: sans; - margin: 0em; - /*padding: 1.5em;*/ - padding: 2mm; - font-size: 1.1em; - box-sizing: border-box; + background-color: #ccc; + color: #666; + font-family: sans; + margin: 0em; + /*padding: 1.5em;*/ + padding: 2mm; + font-size: 1.1em; + box-sizing: border-box; } h2 { - text-transform: uppercase; + text-transform: uppercase; } #overlay { - z-index: 10; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.8); + z-index: 10; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); } .paddingbox { - padding: 2mm; + padding: 2mm; } .nav { - list-style-type: none; - padding: 0em; - margin: 0em; + list-style-type: none; + padding: 0em; + margin: 0em; } .nav li { - display: inline; - padding: 0.1em; - margin-right: 1em; - border-width: 0.1em; - border-color: blue; - border-bottom-style: solid; - cursor: pointer; + display: inline; + padding: 0.1em; + margin-right: 1em; + border-width: 0.1em; + border-color: blue; + border-bottom-style: solid; + cursor: pointer; } td { - padding: 0.3em; - border-style: none; - border-width: 0.1em; - border-color: orange; + padding: 0.3em; + border-style: none; + border-width: 0.1em; + border-color: orange; } li { - list-style: none; + list-style: none; } .btn { - cursor: pointer; - padding: 0.1em; + cursor: pointer; + padding: 0.1em; } -input[type="button"] { - border: solid 1px rgba(0, 0, 0, 0); - border-radius: 10px; - font-size: 6mm; - cursor: pointer; - color: #333; - background-color: #f79e3a; - width: 26vw; - display: inline-grid; - margin: 0.2rem; - padding: 0.5rem; - text-align: center; - text-transform: capitalize; - box-shadow: 5px 5px 9px #aaa; - word-break: break-all; +input[type='button'] { + border: solid 1px rgba(0, 0, 0, 0); + border-radius: 10px; + font-size: 6mm; + cursor: pointer; + color: #333; + background-color: #f79e3a; + width: 26vw; + display: inline-grid; + margin: 0.2rem; + padding: 0.5rem; + text-align: center; + text-transform: capitalize; + box-shadow: 5px 5px 9px #aaa; + word-break: break-all; } .btn2, .box, .btnSmall { - border: solid 1px rgba(0, 0, 0, 0); - border-radius: 10px; - font-size: 6mm; - cursor: pointer; - color: #333; - background-color: #f79e3a; - display: inline-grid; - margin: 0.2rem; - padding: 0.5rem; - text-align: center; - text-transform: capitalize; - box-shadow: 5px 5px 9px #aaa; + border: solid 1px rgba(0, 0, 0, 0); + border-radius: 10px; + font-size: 6mm; + cursor: pointer; + color: #333; + background-color: #f79e3a; + display: inline-grid; + margin: 0.2rem; + padding: 0.5rem; + text-align: center; + text-transform: capitalize; + box-shadow: 5px 5px 9px #aaa; } .btn2, -.box -{ - width: 12rem; +.box { + width: 12rem; } .btnSmall { - width: 7rem; + width: 7rem; } .menu { - border: solid 1px rgba(0, 0, 0, 0); - border-radius: 10px; - font-size: 0.8em; - cursor: pointer; - background-color: #f79e3a; - display: inline-grid; - margin: 0.2rem; - padding: 0.5rem; - text-align: center; - text-transform: capitalize; - width: 7rem; + border: solid 1px rgba(0, 0, 0, 0); + border-radius: 10px; + font-size: 0.8em; + cursor: pointer; + background-color: #f79e3a; + display: inline-grid; + margin: 0.2rem; + padding: 0.5rem; + text-align: center; + text-transform: capitalize; + width: 7rem; } span.menu:nth-child(2) { - background-color: rgba(0, 0, 0, 0); - width: 6.3rem; + background-color: rgba(0, 0, 0, 0); + width: 6.3rem; } span.menu:nth-child(16) { - background-color: rgba(0, 0, 0, 0); + background-color: rgba(0, 0, 0, 0); } li .menu { - background-color: rgba(0,0,0,0.3); + background-color: rgba(0, 0, 0, 0.3); } div.btn2:nth-child(9) { - border: rgba(0, 0, 0, 0); - background-color: rgba(0, 0, 0, 0); - text-decoration: underline; - box-shadow: unset; - margin-top: 1em; - text-align: left !important; - font-size: 1em; + border: rgba(0, 0, 0, 0); + background-color: rgba(0, 0, 0, 0); + text-decoration: underline; + box-shadow: unset; + margin-top: 1em; + text-align: left !important; + font-size: 1em; } div.btn2:nth-child(9):hover { - color: #666; + color: #666; } .btn2:hover { - background-color: orangered; - color: white; + background-color: orangered; + color: white; } .hidden { - display: none; + display: none; } .noBorderTable { - width: 100%; - border: none; - border-collapse: collapse; + width: 100%; + border: none; + border-collapse: collapse; } .noBorderTD { - border: none; - border-collapse: collapse; - vertical-align: center; + border: none; + border-collapse: collapse; + vertical-align: center; } .filelink { - color: inherit; + color: inherit; } input, textarea { - border-color: orange; - font-size: 10mm; - border-radius: 3mm; - border-width: 1mm; - padding: 2mm; - margin-bottom: 1mm; - margin-right: 1mm; - /* make the button the whole screen width */ - width: 100%; - /* make the text input fit small screens*/ - box-sizing: border-box; + border-color: orange; + font-size: 10mm; + border-radius: 3mm; + border-width: 1mm; + padding: 2mm; + margin-bottom: 1mm; + margin-right: 1mm; + /* make the button the whole screen width */ + width: 100%; + /* make the text input fit small screens*/ + box-sizing: border-box; } -input[type="button"]:hover { - background-color: orangered; +input[type='button']:hover { + background-color: orangered; } - /*chat*/ textarea#txtNewMsg { - font-size: 100%; - border-radius: 3mm; - border-width: 1mm; - padding: 2mm; - margin-bottom: 0mm; - margin-right: 1mm; - height: 110px; - resize: none; - /* make the button the whole screen width */ - width: 100%; - /*height: 100%;*/ - /* make the text input fit small screens*/ - box-sizing: border-box; + font-size: 100%; + border-radius: 3mm; + border-width: 1mm; + padding: 2mm; + margin-bottom: 0mm; + margin-right: 1mm; + height: 110px; + resize: none; + /* make the button the whole screen width */ + width: 100%; + /*height: 100%;*/ + /* make the text input fit small screens*/ + box-sizing: border-box; } input#txtMsgKeyword { - /* color: orange; + /* color: orange; font-family: monospace; background-color: black;*/ - border-color: orange; - font-size: 100%; - border-radius: 3mm; - border-width: 1mm; - padding: 1mm; - margin-bottom: 0mm; - margin-right: 1mm; - /* make the button the whole screen width */ - width: 100%; - /* make the text input fit small screens*/ - box-sizing: border-box; + border-color: orange; + font-size: 100%; + border-radius: 3mm; + border-width: 1mm; + padding: 1mm; + margin-bottom: 0mm; + margin-right: 1mm; + /* make the button the whole screen width */ + width: 100%; + /* make the text input fit small screens*/ + box-sizing: border-box; } .checkbox { - width: auto; + width: auto; } .flexbox { - display: -webkit-box; - /* OLD - iOS 6-, Safari 3.1-6 */ - display: -moz-box; - /* OLD - Firefox 19- (buggy but mostly works) */ - display: -ms-flexbox; - /* TWEENER - IE 10 */ - display: -webkit-flex; - /* NEW - Chrome */ - display: flex; - /* NEW, Spec - Opera 12.1, Firefox 20+ */ + display: -webkit-box; + /* OLD - iOS 6-, Safari 3.1-6 */ + display: -moz-box; + /* OLD - Firefox 19- (buggy but mostly works) */ + display: -ms-flexbox; + /* TWEENER - IE 10 */ + display: -webkit-flex; + /* NEW - Chrome */ + display: flex; + /* NEW, Spec - Opera 12.1, Firefox 20+ */ } .flexwidemember { - -webkit-box-flex: 1; - /* OLD - iOS 6-, Safari 3.1-6 */ - -moz-box-flex: 1; - /* OLD - Firefox 19- */ - width: 20%; - /* For old syntax, otherwise collapses. */ - -webkit-flex: 1; - /* Chrome */ - -ms-flex: 1; - /* IE 10 */ - flex: 1; - /* NEW, Spec - Opera 12.1, Firefox 20+ */ + -webkit-box-flex: 1; + /* OLD - iOS 6-, Safari 3.1-6 */ + -moz-box-flex: 1; + /* OLD - Firefox 19- */ + width: 20%; + /* For old syntax, otherwise collapses. */ + -webkit-flex: 1; + /* Chrome */ + -ms-flex: 1; + /* IE 10 */ + flex: 1; + /* NEW, Spec - Opera 12.1, Firefox 20+ */ } #logo_splash { - -webkit-animation-fill-mode: forwards; - /* Chrome, Safari, Opera */ - animation-fill-mode: forwards; - -webkit-animation-name: logo_splash; - /* Chrome, Safari, Opera */ - -webkit-animation-duration: 3s; - /* Chrome, Safari, Opera */ - animation-name: logo_splash; - animation-duration: 3s; - text-align: center; + -webkit-animation-fill-mode: forwards; + /* Chrome, Safari, Opera */ + animation-fill-mode: forwards; + -webkit-animation-name: logo_splash; + /* Chrome, Safari, Opera */ + -webkit-animation-duration: 3s; + /* Chrome, Safari, Opera */ + animation-name: logo_splash; + animation-duration: 3s; + text-align: center; } - /* Chrome, Safari, Opera */ @-webkit-keyframes logo_splash { - from { - opacity: 0; - } - to { - opacity: 1; - } + from { + opacity: 0; + } + to { + opacity: 1; + } } - /* Standard syntax */ @keyframes logo_splash { - from { - opacity: 0; - } - to { - opacity: 1; - } -} \ No newline at end of file + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/webui-src/app/people/people.css b/webui-src/app/people/people.css index 3bb1610..11297c3 100644 --- a/webui-src/app/people/people.css +++ b/webui-src/app/people/people.css @@ -1,47 +1,47 @@ .identity { - color: #444; - font-size: 1.1em; - margin: 20px; - padding: 10px; - border: 1px solid #aaa; - border-radius: 20px; + color: #444; + font-size: 1.1em; + margin: 20px; + padding: 10px; + border: 1px solid #aaa; + border-radius: 20px; } .identity > h4 { - margin: 5px; - font-size: 1.3em; + margin: 5px; + font-size: 1.3em; } .identity p { - margin: 0; + margin: 0; } .identity button { - font-size: 0.9em; + font-size: 0.9em; } img.avatar { - float: left; - height: 100px; - margin-right: 0.3em; + float: left; + height: 100px; + margin-right: 0.3em; } .identity .details { - display: grid; - grid-template-columns: 140px auto; - grid-row-gap: 5px; - justify-content: left; + display: grid; + grid-template-columns: 140px auto; + grid-row-gap: 5px; + justify-content: left; } -.counter{ - margin-left: 0.5em; +.counter { + margin-left: 0.5em; } .counter:before { - content: "("; + content: '('; } .counter:after { - content: ")"; + content: ')'; } -.chatInit{ - margin-left: 0.5em; - color: green; - cursor: pointer; -} \ No newline at end of file +.chatInit { + margin-left: 0.5em; + color: green; + cursor: pointer; +} diff --git a/webui-src/app/people/people.js b/webui-src/app/people/people.js index 4158aeb..52012a9 100644 --- a/webui-src/app/people/people.js +++ b/webui-src/app/people/people.js @@ -1,34 +1,38 @@ -let m = require('mithril'); -let rs = require('rswebui'); +const m = require('mithril'); +const rs = require('rswebui'); -let OwnIds = require('people/people_ownids'); -let people_util = require('people/people_util') +const peopleOwnIds = require('people/people_ownids'); +const peopleUtil = require('people/people_util'); const AllContacts = { - oninit: () => { - }, + oninit: () => {}, view: () => { - let list = people_util.sortUsers(rs.userList.users); + const list = peopleUtil.sortUsers(rs.userList.users); return m('.widget', [ - m('h3', 'Contacts', m('span.counter',list.length)), m('hr'), - list.map((id) => m('.id', { - key: id.mGroupId, - title: 'ID: ' + id.mGroupId, - }, id.mGroupName, m('i.chatInit.fa.fa-comment-medical', { - title: 'start distant chat with ' + id.mGroupName + ' (' + id.mGroupId + ')', - onclick: () => { - m.route.set('/chat/:userid/createdistantchat',{userid:id.mGroupId}); - }, - }))) + m('h3', 'Contacts', m('span.counter', list.length)), + m('hr'), + list.map((id) => + m( + '.id', + { + key: id.mGroupId, + title: 'ID: ' + id.mGroupId, + }, + id.mGroupName, + m('i.chatInit.fa.fa-comment-medical', { + title: 'start distant chat with ' + id.mGroupName + ' (' + id.mGroupId + ')', + onclick: () => { + m.route.set('/chat/:userid/createdistantchat', { userid: id.mGroupId }); + }, + }) + ) + ), ]); }, }; const Layout = { - view: vnode => m('.tab-page', [ - m(OwnIds), - m(AllContacts), - ]) + view: (vnode) => m('.tab-page', [m(peopleOwnIds), m(AllContacts)]), }; module.exports = { @@ -36,4 +40,3 @@ module.exports = { return m(Layout); }, }; - diff --git a/webui-src/app/people/people_ownids.js b/webui-src/app/people/people_ownids.js index 32e3416..55b50be 100644 --- a/webui-src/app/people/people_ownids.js +++ b/webui-src/app/people/people_ownids.js @@ -1,35 +1,35 @@ -let m = require("mithril"); -let rs = require("rswebui"); -let widget = require("widgets"); -let people_util = require("people/people_util"); +const m = require('mithril'); +const rs = require('rswebui'); +const widget = require('widgets'); +const peopleUtil = require('people/people_util'); const SignedIdentiy = () => { - let passphase = ""; + let passphase = ''; return { view: (v) => [ - m("i.fas.fa-user-edit"), - m("h3", "Enter your passpharse"), - m("hr"), + m('i.fas.fa-user-edit'), + m('h3', 'Enter your passpharse'), + m('hr'), - m("input[type=password][placeholder=Passpharse]", { - style: "margin-top:50px;width:80%", + m('input[type=password][placeholder=Passpharse]', { + style: 'margin-top:50px;width:80%', oninput: (e) => { passphase = e.target.value; }, }), m( - "button", + 'button', { - style: "margin-top:160px;", + style: 'margin-top:160px;', onclick: () => { - rs.rsJsonApiRequest("/rsIdentity/getOwnSignedIds", {}, (owns) => { + rs.rsJsonApiRequest('/rsIdentity/getOwnSignedIds', {}, (owns) => { console.log(owns.ids[0]); console.log(v.attrs.name); owns.ids.length > 0 ? rs.rsJsonApiRequest( - "/rsIdentity/createIdentity", + '/rsIdentity/createIdentity', { id: owns.ids[0], name: v.attrs.name, @@ -38,128 +38,120 @@ const SignedIdentiy = () => { }, (data) => { const message = data.retval - ? "Successfully created identity." - : "An error occured while creating identity."; + ? 'Successfully created identity.' + : 'An error occured while creating identity.'; console.log(message); - widget.popupMessage([ - m("h3", "Create new Identity"), - m("hr"), - message, - ]); + 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.', ]); }); }, }, - "Enter" + 'Enter' ), ], }; }; const CreateIdentity = () => { // TODO: set user avatar - let name = "", + let name = '', pseudonimous = false; return { view: (v) => [ - m("i.fas.fa-user-plus"), - m("h3", "Create new Identity"), - m("hr"), - m("input[type=text][placeholder=Name]", { + m('i.fas.fa-user-plus'), + m('h3', 'Create new Identity'), + m('hr'), + m('input[type=text][placeholder=Name]', { value: name, oninput: (e) => (name = e.target.value), }), m( - "div", + 'div', { - style: "display:inline; margin-left:5px;", + style: 'display:inline; margin-left:5px;', }, [ - "Type:", + 'Type:', m( - "select", + 'select', { value: pseudonimous, - style: "border:1px solid black", + style: 'border:1px solid black', oninput: (e) => { - pseudonimous = e.target.value === "true"; + pseudonimous = e.target.value === 'true'; console.log(pseudonimous); }, }, [ - m("option[value=false][selected]", "Linked to your Profile"), - m("option[value=true]", "Pseudonymous"), + m('option[value=false][selected]', 'Linked to your Profile'), + m('option[value=true]', 'Pseudonymous'), ] ), ] ), - m("br"), + m('br'), 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." + '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.' ), m( - "button", + 'button', { onclick: () => { !pseudonimous - ? widget.popupMessage(m(SignedIdentiy, {name: name})) + ? widget.popupMessage(m(SignedIdentiy, { name })) : rs.rsJsonApiRequest( - "/rsIdentity/createIdentity", + '/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, - ]); + ? 'Successfully created identity.' + : 'An error occured while creating identity.'; + widget.popupMessage([m('h3', 'Create new Identity'), m('hr'), message]); } ); }, }, - "Create" + 'Create' ), ], }; }; const SignedEditIdentity = () => { - let passphase = ""; + let passphase = ''; return { view: (v) => [ - m("i.fas.fa-user-edit"), - m("h3", "Enter your passpharse"), - m("hr"), + m('i.fas.fa-user-edit'), + m('h3', 'Enter your passpharse'), + m('hr'), - m("input[type=password][placeholder=Passpharse]", { - style: "margin-top:50px;width:80%", + m('input[type=password][placeholder=Passpharse]', { + style: 'margin-top:50px;width:80%', oninput: (e) => { passphase = e.target.value; }, }), m( - "button", + 'button', { - style: "margin-top:160px;", + style: 'margin-top:160px;', onclick: () => rs.rsJsonApiRequest( - "/rsIdentity/updateIdentity", + '/rsIdentity/updateIdentity', { id: v.attrs.details.mId, name: v.attrs.name, @@ -168,69 +160,61 @@ const SignedEditIdentity = () => { }, (data) => { const message = data.retval - ? "Successfully created identity." - : "An error occured while creating identity."; - widget.popupMessage([ - m("h3", "Create new Identity"), - m("hr"), - message, - ]); + ? 'Successfully created identity.' + : 'An error occured while creating identity.'; + widget.popupMessage([m('h3', 'Create new Identity'), m('hr'), message]); } ), }, - "Enter" + 'Enter' ), ], }; }; const EditIdentity = () => { - let name = ""; + let name = ''; return { view: (v) => [ - m("i.fas.fa-user-edit"), - m("h3", "Edit Identity"), - m("hr"), - m("input[type=text][placeholder=Name]", { + m('i.fas.fa-user-edit'), + m('h3', 'Edit Identity'), + m('hr'), + m('input[type=text][placeholder=Name]', { value: name, oninput: (e) => { name = e.target.value; }, }), - m("canvas"), + m('canvas'), m( - "button", + 'button', { onclick: () => { - !people_util.checksudo(v.attrs.details.mPgpId) + !peopleUtil.checksudo(v.attrs.details.mPgpId) ? widget.popupMessage([ m(SignedEditIdentity, { - name: name, + name, details: v.attrs.details, }), ]) : rs.rsJsonApiRequest( - "/rsIdentity/updateIdentity", + '/rsIdentity/updateIdentity', { id: v.attrs.details.mId, - name: name, - //avatar: v.attrs.details.mAvatar.mData.base64, + 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, - ]); + ? 'Successfully Updated identity.' + : 'An error occured while updating identity.'; + widget.popupMessage([m('h3', 'Update Identity'), m('hr'), message]); } ); }, }, - "Save" + 'Save' ), ], }; @@ -239,33 +223,30 @@ const EditIdentity = () => { const DeleteIdentity = () => { return { view: (v) => [ - m("i.fas.fa-user-times"), - m("h3", "Delete Identity: " + v.attrs.name), - m("hr"), + m('i.fas.fa-user-times'), + m('h3', 'Delete Identity: ' + v.attrs.name), + m('hr'), + m('p', 'Are you sure you want to delete this Identity? It cannot be restore'), m( - "p", - "Are you sure you want to delete this Identity? It cannot be restore" - ), - m( - "button", + 'button', { onclick: () => rs.rsJsonApiRequest( - "/rsIdentity/deleteIdentity", + '/rsIdentity/deleteIdentity', { id: v.attrs.id, }, () => { widget.popupMessage([ - m("i.fas.fa-user-edit"), - m("h3", "Delete Identity: " + v.attrs.name), - m("hr"), - m("p", "Identity Deleted successfuly."), + m('i.fas.fa-user-edit'), + m('h3', 'Delete Identity: ' + v.attrs.name), + m('hr'), + m('p', 'Identity Deleted successfuly.'), ]); } ), }, - "Confirm" + 'Confirm' ), ], }; @@ -281,7 +262,7 @@ const Identity = () => { return { oninit: (v) => rs.rsJsonApiRequest( - "/rsIdentity/getIdDetails", + '/rsIdentity/getIdDetails', { id: v.attrs.id, }, @@ -289,56 +270,54 @@ const Identity = () => { details = data.details; // Creating URI during fetch because `details` is uninitialized // during view run, due to request being async. - avatarURI = people_util.createAvatarURI(data.details.mAvatar); + avatarURI = peopleUtil.createAvatarURI(data.details.mAvatar); } ), view: (v) => m( - ".identity", + '.identity', { key: details.mId, }, [ - m("h4", details.mNickname), + m('h4', details.mNickname), m(avatarURI), - m(".details", [ - m("p", "ID:"), - m("p", details.mId), - m("p", "Type:"), - m("p", details.mFlags === 14 ? "Signed ID" : "Anonymous ID"), - m("p", "Owner node ID:"), - m("p", details.mPgpId), - m("p", "Created on:"), + m('.details', [ + m('p', 'ID:'), + m('p', details.mId), + m('p', 'Type:'), + m('p', details.mFlags === 14 ? 'Signed ID' : 'Anonymous ID'), + m('p', 'Owner node ID:'), + m('p', details.mPgpId), + m('p', 'Created on:'), m( - "p", - typeof details.mPublishTS === "object" + 'p', + typeof details.mPublishTS === 'object' ? new Date(details.mPublishTS.xint64 * 1000).toLocaleString() - : "undefiend" + : 'undefiend' ), - m("p", "Last used:"), + m('p', 'Last used:'), m( - "p", - typeof details.mLastUsageTS === "object" - ? new Date( - details.mLastUsageTS.xint64 * 1000 - ).toLocaleDateString() - : "undefiend" + 'p', + typeof details.mLastUsageTS === 'object' + ? new Date(details.mLastUsageTS.xint64 * 1000).toLocaleDateString() + : 'undefiend' ), ]), m( - "button", + 'button', { onclick: () => widget.popupMessage( m(EditIdentity, { - details: details, + details, }) ), }, - "Edit" + 'Edit' ), m( - "button.red", + 'button.red', { onclick: () => widget.popupMessage( @@ -348,7 +327,7 @@ const Identity = () => { }) ), }, - "Delete" + 'Delete' ), ] ), @@ -358,18 +337,18 @@ const Identity = () => { const Layout = () => { let ownIds = []; return { - oninit: () => people_util.ownIds((data) => (ownIds = data)), + oninit: () => peopleUtil.ownIds((data) => (ownIds = data)), view: () => - m(".widget", [ - m("h3", "Own Identities", m("span.counter", ownIds.length)), - m("hr"), + m('.widget', [ + m('h3', 'Own Identities', m('span.counter', ownIds.length)), + m('hr'), m( - "button", + 'button', { onclick: () => widget.popupMessage(m(CreateIdentity)), }, - "New Identity" + 'New Identity' ), ownIds.map((id) => m(Identity, { diff --git a/webui-src/app/people/people_util.js b/webui-src/app/people/people_util.js index e44eebb..cedfdf7 100644 --- a/webui-src/app/people/people_util.js +++ b/webui-src/app/people/people_util.js @@ -1,34 +1,13 @@ -let m = require("mithril"); -let rs = require("rswebui"); - -function getInfo(id) { - let name = ""; - let imageURI = ""; - rs.rsJsonApiRequest( - "/rsIdentity/getIdentitiesInfo", - { - id, - }, - function (data) { - console.log("data: ", data.idsInfo); - //name = data.details.mNickname - //imageURI = data.details.mAvatar.mData; - } - ); - //return m('.profile-detail', [ - //m('img[src=data:image/png;base64,' + imageURI + ']'), - // name//, - //]); - return "name"; -} +const m = require('mithril'); +const rs = require('rswebui'); function checksudo(id) { - return id === "0000000000000000"; + return id === '0000000000000000'; } function sortUsers(list) { if (list !== undefined) { - let result = [...list]; + const result = [...list]; result.sort((a, b) => a.mGroupName.localeCompare(b.mGroupName)); return result; } @@ -37,21 +16,19 @@ function sortUsers(list) { function sortIds(list) { if (list !== undefined) { - let result = [...list]; - result.sort((a, b) => - rs.userList.username(a).localeCompare(rs.userList.username(b)) - ); + const result = [...list]; + result.sort((a, b) => rs.userList.username(a).localeCompare(rs.userList.username(b))); return result; } return list; } function ownIds(consumer = (list) => {}, onlySigned = false) { - rs.rsJsonApiRequest("/rsIdentity/getOwnSignedIds", {}, (owns) => { + rs.rsJsonApiRequest('/rsIdentity/getOwnSignedIds', {}, (owns) => { if (onlySigned) { consumer(sortIds(owns.ids)); } else { - rs.rsJsonApiRequest("/rsIdentity/getOwnPseudonimousIds", {}, (pseudo) => + rs.rsJsonApiRequest('/rsIdentity/getOwnPseudonimousIds', {}, (pseudo) => consumer(sortIds(pseudo.ids.concat(owns.ids))) ); } @@ -59,23 +36,22 @@ function ownIds(consumer = (list) => {}, onlySigned = false) { } function createAvatarURI(avatar) { - return avatar.mData.base64 === "" + return avatar.mData.base64 === '' ? { view: () => [], } : { view: () => - m("img.avatar", { - src: "data:image/png;base64," + avatar.mData.base64, + m('img.avatar', { + src: 'data:image/png;base64,' + avatar.mData.base64, }), }; } module.exports = { - getInfo, sortUsers, sortIds, ownIds, createAvatarURI, - checksudo + checksudo, }; diff --git a/webui-src/app/rswebui.js b/webui-src/app/rswebui.js index 1cb58a8..d4d0b11 100644 --- a/webui-src/app/rswebui.js +++ b/webui-src/app/rswebui.js @@ -1,11 +1,11 @@ const m = require('mithril'); const API_URL = 'http://127.0.0.1:9092'; -let loginKey = { +const loginKey = { username: '', passwd: '', isVerified: false, - url: API_URL + url: API_URL, }; // Make this as object property? @@ -28,8 +28,7 @@ function rsJsonApiRequest( ) { headers['Accept'] = 'application/json'; if (loginKey.isVerified) { - headers['Authorization'] = - 'Basic ' + btoa(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 @@ -39,21 +38,21 @@ function rsJsonApiRequest( method: 'POST', url: loginKey.url + path, async, - extract: xhr => { + extract: (xhr) => { // Empty string is not valid json and fails on parse - let response = xhr.responseText || '""'; + const response = xhr.responseText || '""'; return { status: xhr.status, statusText: xhr.statusText, - body: handleDeserialize(response) + body: handleDeserialize(response), }; }, serialize: handleSerialize, - headers: headers, + headers, body: data, - config: config + config, }) - .then(result => { + .then((result) => { if (result.status === 200) { callback(result.body, true); } else { @@ -63,14 +62,9 @@ function rsJsonApiRequest( } return result; }) - .catch(function(e) { + .catch(function (e) { callback(e, false); - console.error( - 'Error: While sending request for path:', - path, - '\ninfo:', - e - ); + console.error('Error: While sending request for path:', path, '\ninfo:', e); m.route.set('/'); }); } @@ -91,81 +85,88 @@ function setBackgroundTask(task, interval, taskInScope) { } function computeIfMissing(map, key, missing = () => ({})) { - if (!map.hasOwnProperty(key)) { - map[key] = missing(); - } - return map[key]; + if (!Object.prototype.hasOwnProperty.call(map, key)) { + map[key] = missing(); + } + return map[key]; } function deeperIfExist(map, key, action) { - if (map.hasOwnProperty(key)) { - action(map[key]); - return true; - } else { - return false; - } + if (Object.prototype.hasOwnProperty.call(map, key)) { + action(map[key]); + return true; + } else { + return false; + } } const eventQueue = { - events: { - 15: { // Chat-Messages - types: { -// #define RS_CHAT_TYPE_PUBLIC 1 -// #define RS_CHAT_TYPE_PRIVATE 2 - 2: chat_id => chat_id.distant_chat_id, // distant chat (initiate? -> todo accept) -// #define RS_CHAT_TYPE_LOBBY 3 - 3: chat_id => chat_id.lobby_id.xstr64, // lobby_id -// #define RS_CHAT_TYPE_DISTANT 4 - }, - messages: {}, - chatMessages: (chat_id, owner, action) => { - if (!deeperIfExist( - owner.types, - chat_id.type, - keyfn => action(computeIfMissing(computeIfMissing(owner.messages,chat_id.type), keyfn(chat_id),()=>[])) - )) { - console.info('unknown chat event', chat_id); - }; - }, - 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: () => {}, - }, - 8: { // Circles (ignore in the meantime) - handler: (event,owner) => {}, + events: { + 15: { + // Chat-Messages + types: { + // #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 + }, + messages: {}, + chatMessages: (chatId, owner, action) => { + if ( + !deeperIfExist(owner.types, chatId.type, (keyfn) => + action( + computeIfMissing( + computeIfMissing(owner.messages, chatId.type), + keyfn(chatId), + () => [] + ) + ) + ) + ) { + console.info('unknown chat event', chatId); } + }, + 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 => { - if (!deeperIfExist(eventQueue.events, event.mType, owner => owner.handler(event, owner))) { - console.info('unhandled event', event); - }; + 8: { + // Circles (ignore in the meantime) + handler: (event, owner) => {}, }, -} + }, + handler: (event) => { + if (!deeperIfExist(eventQueue.events, event.mType, (owner) => owner.handler(event, owner))) { + console.info('unhandled event', event); + } + }, +}; const userList = { users: [], userMap: {}, loadUsers: () => { - rsJsonApiRequest('/rsIdentity/getIdentitiesSummaries', {}, - list=> { - if (list!== undefined) { - console.info('loading ' + list.ids.length + ' users ...'); - userList.users=list.ids; - userList.userMap = list.ids.reduce((a,c) => { - a[c.mGroupId] = c.mGroupName; - return a; - }, {}); - } + rsJsonApiRequest('/rsIdentity/getIdentitiesSummaries', {}, (list) => { + if (list !== undefined) { + console.info('loading ' + list.ids.length + ' users ...'); + userList.users = list.ids; + userList.userMap = list.ids.reduce((a, c) => { + a[c.mGroupId] = c.mGroupName; + return a; + }, {}); } - ); + }); }, - username: id => { - return userList.userMap[id]||id; + username: (id) => { + return userList.userMap[id] || id; }, -} +}; /* path, @@ -177,52 +178,77 @@ const userList = { handleSerialize = JSON.stringify config */ -function startEventQueue(info, loginHeader = {}, displayAuthError = () => {}, displayErrorMessage = () => {}, successful = () => {}){ - return rsJsonApiRequest('/rsEvents/registerEventsHandler', {}, +function startEventQueue( + info, + loginHeader = {}, + displayAuthError = () => {}, + displayErrorMessage = () => {}, + successful = () => {} +) { + return rsJsonApiRequest( + '/rsEvents/registerEventsHandler', + {}, (data, success) => { if (success) { // unused - } else if (data.status == 401) { + } else if (data.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 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, + true, + loginHeader, + JSON.parse, + JSON.stringify, (xhr, args, url) => { let lastIndex = 0; - xhr.onprogress = ev => { - let currIndex = xhr.responseText.length; + xhr.onprogress = (ev) => { + const currIndex = xhr.responseText.length; if (currIndex > lastIndex) { - let parts = xhr.responseText.substring(lastIndex,currIndex); - lastIndex=currIndex; - for(data of parts.trim().split('\n\n').filter(e=> e.startsWith('data: {')).map(e=> e.substr(6)).map(JSON.parse)){ - if (data.hasOwnProperty('retval')) { - console.info(info + ' [' + data.retval.errorCategory + '] ' + data.retval.errorMessage); + const parts = xhr.responseText.substring(lastIndex, currIndex); + lastIndex = currIndex; + for (const data of parts + .trim() + .split('\n\n') + .filter((e) => e.startsWith('data: {')) + .map((e) => e.substr(6)) + .map(JSON.parse)) { + if (Object.prototype.hasOwnProperty.call(data, 'retval')) { + console.info( + info + ' [' + data.retval.errorCategory + '] ' + data.retval.errorMessage + ); if (data.retval.errorNumber === 0) { successful(); } else { - displayErrorMessage(info + ' failed: [' + data.retval.errorCategory + '] ' + data.retval.errorMessage); + displayErrorMessage( + info + ' failed: [' + data.retval.errorCategory + '] ' + data.retval.errorMessage + ); } - } else if(data.hasOwnProperty('event')) { + } else if (Object.prototype.hasOwnProperty.call(data, 'event')) { data.event.queueSize = currIndex; eventQueue.handler(data.event); } } - if (currIndex > 1e5) { // max 100 kB eventQueue + if (currIndex > 1e5) { + // max 100 kB eventQueue startEventQueue('restart queue'); xhr.abort(); } } - } + }; return xhr; } ); } -function logon(loginHeader, displayAuthError, displayErrorMessage, successful){ +function logon(loginHeader, displayAuthError, displayErrorMessage, successful) { startEventQueue('login', loginHeader, displayAuthError, displayErrorMessage, () => { successful(); userList.loadUsers(); diff --git a/webui-src/app/theme.css b/webui-src/app/theme.css index 69b0990..4ef3941 100644 --- a/webui-src/app/theme.css +++ b/webui-src/app/theme.css @@ -24,9 +24,9 @@ General site-wide rules animation: fadein 0.5s; } -input[type="text"], -input[type="password"], -input[type="number"], +input[type='text'], +input[type='password'], +input[type='number'], textarea { box-sizing: border-box; background: white; @@ -130,7 +130,7 @@ hr { grid-row-gap: 16px; justify-content: start; } -.grid-2col input[type="checkbox"] { +.grid-2col input[type='checkbox'] { margin-top: 20px; } diff --git a/webui-src/app/widgets.js b/webui-src/app/widgets.js index ad226e4..7a9ba83 100644 --- a/webui-src/app/widgets.js +++ b/webui-src/app/widgets.js @@ -1,16 +1,23 @@ -let m = require('mithril'); - +const m = require('mithril'); const Sidebar = () => { let active = 0; return { - view: (v) => m('.sidebar', - v.attrs.tabs.map((panelName, index) => m(m.route.Link, { - class: index === active? 'selected-sidebar-link': '', - onclick: () => (active = index), - href: v.attrs.baseRoute + panelName - }, panelName)), - ), + view: (v) => + m( + '.sidebar', + v.attrs.tabs.map((panelName, index) => + m( + m.route.Link, + { + class: index === active ? 'selected-sidebar-link' : '', + onclick: () => (active = index), + href: v.attrs.baseRoute + panelName, + }, + panelName + ) + ) + ), }; }; @@ -20,13 +27,19 @@ const Sidebar = () => { function popupMessage(message) { const container = document.getElementById('modal-container'); container.style.display = 'block'; - m.render(container, + m.render( + container, m('.modal-content', [ - m('button.red', { - onclick: () => container.style.display = 'none' - }, m('i.fas.fa-times')), + m( + 'button.red', + { + onclick: () => (container.style.display = 'none'), + }, + m('i.fas.fa-times') + ), message, - ])); + ]) + ); } module.exports = {