-
Notifications
You must be signed in to change notification settings - Fork 4
Support inbox messages #100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 7 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
e4098ec
Support inbox messages
clabland 96df16c
Merge branch 'develop' into INAPP-13667
clabland c79aa02
Review feedback
clabland d24529f
lint
clabland 27c222d
Send updated message list when marking opened or deleted
clabland b41616d
Support marking unopened and unsubscribing from event emitter
clabland 860f642
Send opened inboxMessageAction events; add test harness config section
clabland 9988768
Review feedback
clabland File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| async function refreshInboxMessages(messages) { | ||
| const unopenedCount = await window.Gist.getInboxUnopenedCount(); | ||
| const badge = document.getElementById('inboxBadge'); | ||
| if (unopenedCount > 0) { | ||
| badge.textContent = unopenedCount; | ||
| badge.style.display = 'inline-block'; | ||
| } else { | ||
| badge.style.display = 'none'; | ||
| } | ||
|
|
||
| if (!messages) { | ||
| messages = await window.Gist.getInboxMessages(); | ||
| } | ||
|
|
||
| const content = document.getElementById('inboxPanelContent'); | ||
|
|
||
| if (messages.length === 0) { | ||
| content.innerHTML = '<p class="no-messages">No messages</p>'; | ||
| return; | ||
| } | ||
|
|
||
| let html = ''; | ||
| for (const message of messages) { | ||
| const props = message.properties || {}; | ||
| const queueId = message.queueId; | ||
| const propertiesJson = JSON.stringify(props, null, 2); | ||
|
|
||
| html += ` | ||
| <div class="inbox-message ${!message.opened ? 'unopened' : ''}" data-queue-id="${queueId}"> | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <div class="inbox-message-header"> | ||
| <strong>Properties</strong> | ||
| <p>Sent at ${new Date(message.sentAt).toLocaleString()}</p> | ||
| ${!message.opened ? '<span class="unopened-dot"></span>' : ''} | ||
| </div> | ||
| <div class="inbox-message-body"> | ||
| <pre>${propertiesJson}</pre> | ||
| </div> | ||
| <div class="inbox-message-actions"> | ||
| ${!message.opened ? `<button onclick="updateInboxMessageOpenState('${queueId}',true)">Mark as opened</button>` : `<button onclick="updateInboxMessageOpenState('${queueId}',false)">Mark as unopened</button>`} | ||
| <button onclick="deleteMessage('${queueId}')">Delete</button> | ||
| </div> | ||
| </div> | ||
| `; | ||
| } | ||
|
|
||
| content.innerHTML = html; | ||
| } | ||
|
|
||
| // eslint-disable-next-line no-unused-vars | ||
| async function updateInboxMessageOpenState(queueId, opened) { | ||
| try { | ||
| await window.Gist.updateInboxMessageOpenState(queueId, opened); | ||
| } catch (error) { | ||
| console.error('Failed to mark message as opened:', error); | ||
| alert('Failed to mark message as read. Please try again.'); | ||
| } | ||
| } | ||
|
|
||
| // eslint-disable-next-line no-unused-vars | ||
| async function deleteMessage(queueId) { | ||
| try { | ||
| await window.Gist.removeInboxMessage(queueId); | ||
| } catch (error) { | ||
| console.error('Failed to delete message:', error); | ||
| alert('Failed to delete message. Please try again.'); | ||
| } | ||
| } | ||
|
|
||
| document.querySelectorAll(".toggle-inbox").forEach(element => { | ||
| element.addEventListener("click", () => { | ||
| const panel = document.getElementById('inboxPanel'); | ||
| if (panel.style.display === 'none') { | ||
| panel.style.display = 'block'; | ||
| } else { | ||
| panel.style.display = 'none'; | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| refreshInboxMessages(); | ||
|
|
||
| window.Gist.events.on('messageInboxUpdated', async function(messages) { | ||
| await refreshInboxMessages(messages); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,24 @@ | |
| <link href="styles.css" rel="stylesheet"> | ||
| </head> | ||
| <body> | ||
| <div class="inbox-header"> | ||
| <div class="inbox-icon-container toggle-inbox"> | ||
| <svg class="inbox-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||
| <polyline points="22 12 16 12 14 15 10 15 8 12 2 12"></polyline> | ||
| <path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path> | ||
| </svg> | ||
| <span class="inbox-badge" id="inboxBadge" style="display: none;">0</span> | ||
| </div> | ||
| </div> | ||
| <div class="inbox-panel" id="inboxPanel" style="display: none;"> | ||
| <div class="inbox-panel-header"> | ||
| <h3>Inbox Messages</h3> | ||
| <button class="close-btn toggle-inbox">×</button> | ||
| </div> | ||
| <div class="inbox-panel-content" id="inboxPanelContent"> | ||
| <p class="no-messages">No messages</p> | ||
| </div> | ||
| </div> | ||
| <div id="banner"></div> | ||
| <div class="row header"> | ||
| <h1>Gist for Web</h1> | ||
|
|
@@ -26,15 +44,79 @@ <h1>Gist for Web</h1> | |
| <div class="row docs"> | ||
| <p>More information can be found on our <a target="_blank" href="https://docs.gist.build">docs</a>, if you have any question you can email us at <a target="_blank" href="mailto:[email protected]">[email protected]</a></p> | ||
| </div> | ||
| <div class="config-form-sticky"> | ||
| <div class="config-form-header" onclick="toggleConfigForm()"> | ||
| <span>⚙️ Configuration Override</span> | ||
| <span id="configToggleIcon">▼</span> | ||
| </div> | ||
| <div class="config-form-content" id="configFormContent"> | ||
| <form id="configForm" onsubmit="saveConfig(event)"> | ||
| <div class="form-section"> | ||
| <h4>Gist.setup()</h4> | ||
| <div class="form-field"> | ||
| <label for="siteId">Site ID:</label> | ||
| <input type="text" id="siteId" name="siteId" /> | ||
| </div> | ||
| <div class="form-field"> | ||
| <label for="dataCenter">Data Center:</label> | ||
| <select id="dataCenter" name="dataCenter"> | ||
| <option value="us">us</option> | ||
| <option value="eu">eu</option> | ||
| </select> | ||
| </div> | ||
| <div class="form-field"> | ||
| <label for="env">Environment:</label> | ||
| <select id="env" name="env"> | ||
| <option value="prod">prod</option> | ||
| <option value="dev">dev</option> | ||
| </select> | ||
| </div> | ||
| <div class="form-field"> | ||
| <label for="logging"> | ||
| <input type="checkbox" id="logging" name="logging" /> | ||
| Logging | ||
| </label> | ||
| </div> | ||
| <div class="form-field"> | ||
| <label for="useAnonymousSession"> | ||
| <input type="checkbox" id="useAnonymousSession" name="useAnonymousSession" /> | ||
| Use Anonymous Session | ||
| </label> | ||
| </div> | ||
| </div> | ||
| <div class="form-section"> | ||
| <h4>Gist.setUserToken()</h4> | ||
| <div class="form-field"> | ||
| <label for="userToken">User Token:</label> | ||
| <input type="text" id="userToken" name="userToken" /> | ||
| </div> | ||
| </div> | ||
| <div class="form-actions"> | ||
| <button type="submit" class="button primary">Save & Reload</button> | ||
| <button type="button" class="button" onclick="resetConfig()">Reset to Defaults</button> | ||
| </div> | ||
| </form> | ||
| </div> | ||
| </div> | ||
| <script src="settings.js"></script> | ||
| <script src="../dist/gist.js"></script> | ||
| <script type="text/javascript"> | ||
| var EncodedTestHTMLMessage = "eNrdWG1v2zYQ/p5fwWkfkgCWZcd2V7i2hy1dVwxpMyAphn0aKPEkcaFIgaT80qL/fUfLSRRFcpVsKwoTdiKRx7vjPc+RPM++e315fv3n77+Q1GZicTRz/4igMpl7ID3XAZQtjgi2WQaWkiil2oCdex+u3/gvveqQpBnMvSWHVa609UikpAWJoivObDpnsOQR+NuXHuGSW06FbyIqYD7sD25VWW4FLH67unxPrCJvr99dkBW3KTm/upoF5WApaOzm9tm1ULEN+XT36lqMDvgxzbjYTMlPGs31iKHS+AY0j1/dyX4+unvsZ2AMTeCNxsXU1IU0ukm0KiTzIyWUnpLv47iipvRCM9C+powXZkqGg3z9UCCjOuFySs4ejeSUMS6TpqFQrX2TUqZWUzIgZ/majPGrk5CeDHpk9+kPT5uXFPM1sAtu7B+cJWBrq2Lc5IJigGIBNbOux2dcQ2S5Qp9x1UUm9xh5qzT/iEGn4jnm/i6M5fHG3/GmFPFBsmaLdOvVzgjPkpqhLc+mZPQomCnwJLVNI1GhjYM1Vxw90M1mLaybV7Zlm+EfAWF/+Uj1jjCj0aiZD44pZLAntr9qzvZHNEGJh8pdj28hw3ELfgkfslJDDtSeTHpkGOvThikJzevcbY18PxQqumn0rCFhBoMfwi/kzLgeu6dQJoKHyLlGBU+kzzEOplmgFXfXrMYdg5cJUF8QJt3IEKAGnhioaaqWoLuEa/IiHHVR3omWL9poOdi2lv1o0sYDU+glbK7cDn5BQxCmhZkdcDM5xbMhBLsCaNthlEJwrnGdNTNu6f4W5GZ4qxEYd0rMilnaCNXWJINIaVoyA0EDLbiEzjuK+zsLdofYLCjP2Zk7xXbnG+NLEglqzNyrHkre/ZFXFamQqyJRl6odBTXJRummPb1hXn1ulZ0eUfJc8OjmbiV9pEbGjTk5fdWia6vP7epGR3MvtTY30yBA1WBNP0FP+mHBBQtudzcT8AwVm8DRFjCDhDLQz2XiYf7jHeTcvZOfC2uVbHM/QP8bItLS3Sn4TdL3qeotrlNuCH4oKZOJ3K6nvPRQ4ggmMQouyXokVSuyUoVgZKMKNwaE2x+/luNfZk/rrPvTa8+sZ3Lok7t4To8vcydNhsef95LqeWH4b8LSlRXDFugadXQT7SDWReTfonN2AOicHSw6owNAZ3Sw6IwPAJ3xwaIzOQB0Jt8iOnuGv9bFpxtO1RmPSqMnXHuqiLxGouEN0MQc2BNZXFVz1VHHNxDr+zLPW1zgZR9vxMYSozIgWF/dYAnf7j19nKwqB/lBi5NjV0JgBZEolQjoRyo77pFdBsccS25VWD9TGrDbUu1q9OO/QkHlDea1t7hENYRLvJBLWKFAOAtoexiV/p99rLqENabGqhfr9QRaveqOXq2r8rp7nAVlkYo1q/vN+B8CSHpg"; | ||
|
|
||
| // Initialize Gist with config | ||
| const config = getConfig(); | ||
| /* | ||
| Dev Setup Options | ||
| { siteId: "siteid", dataCenter: "us", env: "dev", logging: true } | ||
| */ | ||
| Gist.setup({ siteId: "a5ec106751ef4b34a0b9", dataCenter: "eu", logging: true, env: "prod", useAnonymousSession: true }); | ||
| Gist.setUserToken("ABC123"); | ||
| Gist.setup({ | ||
| siteId: config.siteId, | ||
| dataCenter: config.dataCenter, | ||
| logging: config.logging, | ||
| env: config.env, | ||
| useAnonymousSession: config.useAnonymousSession | ||
| }); | ||
| Gist.setUserToken(config.userToken); | ||
| Gist.setCurrentRoute("home"); | ||
|
|
||
| Gist.events.on("messageShown", message => { | ||
|
|
@@ -53,6 +135,10 @@ <h1>Gist for Web</h1> | |
| console.log(`onMessageAction, Action: ${params.action} with name ${params.name} on route: ${params.message.currentRoute} with instanceId: ${params.message.instanceId}`); | ||
| }); | ||
|
|
||
| Gist.events.on("inboxMessageAction", params => { | ||
| console.log(`onInboxMessageAction, Action: ${params.action}, Inbox Message:`, params.message); | ||
| }); | ||
|
|
||
| function showHTMLMessage() { | ||
| Gist.showMessage( | ||
| { | ||
|
|
@@ -122,5 +208,6 @@ <h1>Gist for Web</h1> | |
| Gist.setCustomAttribute("cio_anonymous_id", "123456"); | ||
| } | ||
| </script> | ||
| <script src="inbox.js"></script> | ||
| </body> | ||
| </html> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| // Default configuration | ||
| const defaultConfig = { | ||
| siteId: "a5ec106751ef4b34a0b9", | ||
| dataCenter: "eu", | ||
| env: "prod", | ||
| logging: true, | ||
| useAnonymousSession: true, | ||
| userToken: "ABC123" | ||
| }; | ||
|
|
||
| // Load configuration from localStorage or use defaults | ||
| function loadConfig() { | ||
| const savedConfig = localStorage.getItem('gistConfig'); | ||
| return savedConfig ? JSON.parse(savedConfig) : defaultConfig; | ||
| } | ||
|
|
||
| // Get current configuration | ||
| // eslint-disable-next-line no-unused-vars | ||
| function getConfig() { | ||
| return loadConfig(); | ||
| } | ||
|
|
||
| // Populate form with current config | ||
| function populateConfigForm() { | ||
| const config = loadConfig(); | ||
| document.getElementById('siteId').value = config.siteId; | ||
| document.getElementById('dataCenter').value = config.dataCenter; | ||
| document.getElementById('env').value = config.env; | ||
| document.getElementById('logging').checked = config.logging; | ||
| document.getElementById('useAnonymousSession').checked = config.useAnonymousSession; | ||
| document.getElementById('userToken').value = config.userToken; | ||
| } | ||
|
|
||
| // Toggle config form visibility | ||
| // eslint-disable-next-line no-unused-vars | ||
| function toggleConfigForm() { | ||
| const content = document.getElementById('configFormContent'); | ||
| const icon = document.getElementById('configToggleIcon'); | ||
| if (content.style.display === 'none' || !content.style.display) { | ||
| content.style.display = 'block'; | ||
| icon.textContent = '▲'; | ||
| } else { | ||
| content.style.display = 'none'; | ||
| icon.textContent = '▼'; | ||
| } | ||
| } | ||
|
|
||
| // Save config and reload page | ||
| // eslint-disable-next-line no-unused-vars | ||
| function saveConfig(event) { | ||
| event.preventDefault(); | ||
| const newConfig = { | ||
| siteId: document.getElementById('siteId').value, | ||
| dataCenter: document.getElementById('dataCenter').value, | ||
| env: document.getElementById('env').value, | ||
| logging: document.getElementById('logging').checked, | ||
| useAnonymousSession: document.getElementById('useAnonymousSession').checked, | ||
| userToken: document.getElementById('userToken').value | ||
| }; | ||
| localStorage.setItem('gistConfig', JSON.stringify(newConfig)); | ||
| window.location.reload(); | ||
| } | ||
|
|
||
| // Reset to default config | ||
| // eslint-disable-next-line no-unused-vars | ||
| function resetConfig() { | ||
| if (confirm('Are you sure you want to reset to default configuration?')) { | ||
| localStorage.removeItem('gistConfig'); | ||
| window.location.reload(); | ||
| } | ||
| } | ||
|
|
||
| // Initialize form on page load | ||
| window.addEventListener('DOMContentLoaded', function() { | ||
| populateConfigForm(); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Badge count and message list use different data sources
When
refreshInboxMessagesis called with amessagesparameter (from themessageInboxUpdatedevent), the badge count is still fetched separately viagetInboxUnopenedCount()which reads from localStorage. This causes the badge count and the rendered message list to potentially show inconsistent data - the badge reflects filtered/stored messages while the list renders the event's messages. The unopened count calculation needs to use the samemessagesarray when it's provided.