Handle PDFs and logged out state
This commit is contained in:
@ -34,31 +34,16 @@ function getCurrentTab() {
|
||||
})
|
||||
}
|
||||
|
||||
function setupConnection(xhr) {
|
||||
xhr.setRequestHeader('Content-Type', 'application/json')
|
||||
if (authToken) {
|
||||
xhr.setRequestHeader('Authorization', authToken)
|
||||
}
|
||||
}
|
||||
|
||||
/* other code */
|
||||
function uploadFile({ id, uploadSignedUrl }, contentType, contentObjUrl) {
|
||||
return fetch(contentObjUrl)
|
||||
.then((r) => r.blob())
|
||||
.then((blob) => {
|
||||
return new Promise((resolve) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.open('PUT', uploadSignedUrl, true)
|
||||
xhr.setRequestHeader('Content-Type', contentType)
|
||||
|
||||
xhr.onerror = () => {
|
||||
resolve(undefined)
|
||||
}
|
||||
xhr.onload = () => {
|
||||
// Uploaded.
|
||||
resolve({ id })
|
||||
}
|
||||
xhr.send(blob)
|
||||
return fetch(uploadSignedUrl, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
},
|
||||
body: blob,
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
@ -67,104 +52,111 @@ function uploadFile({ id, uploadSignedUrl }, contentType, contentObjUrl) {
|
||||
})
|
||||
}
|
||||
|
||||
function savePdfFile(tab, url, contentType, contentObjUrl) {
|
||||
return new Promise((resolve) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.onreadystatechange = async function () {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
const { data } = JSON.parse(xhr.response)
|
||||
if ('errorCodes' in data.uploadFileRequest) {
|
||||
if (data.uploadFileRequest.errorCodes[0] === 'UNAUTHORIZED') {
|
||||
clearClickCompleteState()
|
||||
browserApi.tabs.sendMessage(tab.id, {
|
||||
action: ACTIONS.ShowMessage,
|
||||
payload: {
|
||||
text: 'Unable to save page',
|
||||
type: 'error',
|
||||
errorCode: 401,
|
||||
url: omnivoreURL,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!data.uploadFileRequest ||
|
||||
!data.uploadFileRequest.id ||
|
||||
!data.uploadFileRequest.createdPageId ||
|
||||
'errorCodes' in data.uploadFileRequest
|
||||
) {
|
||||
browserApi.tabs.sendMessage(tab.id, {
|
||||
action: ACTIONS.ShowMessage,
|
||||
payload: {
|
||||
text: 'Unable to save page',
|
||||
type: 'error',
|
||||
},
|
||||
})
|
||||
} else {
|
||||
const result = await uploadFile(
|
||||
data.uploadFileRequest,
|
||||
contentType,
|
||||
contentObjUrl
|
||||
)
|
||||
URL.revokeObjectURL(contentObjUrl)
|
||||
|
||||
if (!result) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const createdPageId = data.uploadFileRequest.createdPageId
|
||||
browserApi.tabs.sendMessage(tab.id, {
|
||||
action: ACTIONS.UpdateStatus,
|
||||
payload: {
|
||||
status: 'success',
|
||||
target: 'page',
|
||||
requestId: createdPageId,
|
||||
},
|
||||
})
|
||||
return resolve(data.uploadFileRequest)
|
||||
}
|
||||
} else if (xhr.status === 400) {
|
||||
browserApi.tabs.sendMessage(tab.id, {
|
||||
action: ACTIONS.ShowMessage,
|
||||
payload: {
|
||||
text: 'Unable to save page',
|
||||
type: 'error',
|
||||
},
|
||||
})
|
||||
async function uploadFileRequest(url, contentType) {
|
||||
const data = JSON.stringify({
|
||||
query: `mutation UploadFileRequest($input: UploadFileRequestInput!) {
|
||||
uploadFileRequest(input:$input) {
|
||||
... on UploadFileRequestError {
|
||||
errorCodes
|
||||
}
|
||||
... on UploadFileRequestSuccess {
|
||||
id
|
||||
createdPageId
|
||||
uploadSignedUrl
|
||||
}
|
||||
resolve(false)
|
||||
}
|
||||
}`,
|
||||
variables: {
|
||||
input: {
|
||||
url,
|
||||
contentType,
|
||||
createPageEntry: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const field = 'uploadFileRequest'
|
||||
const result = await gqlRequest(omnivoreGraphqlURL + 'graphql', data)
|
||||
|
||||
if (result[field]['errorCodes']) {
|
||||
if (result[field]['errorCodes'][0] === 'UNAUTHORIZED') {
|
||||
browserApi.tabs.sendMessage(currentTab.id, {
|
||||
action: ACTIONS.UpdateStatus,
|
||||
payload: {
|
||||
target: 'logged_out',
|
||||
status: 'logged_out',
|
||||
message: 'You are not logged in.',
|
||||
ctx: toolbarCtx,
|
||||
},
|
||||
})
|
||||
clearClickCompleteState()
|
||||
} else {
|
||||
browserApi.tabs.sendMessage(currentTab.id, {
|
||||
action: ACTIONS.UpdateStatus,
|
||||
payload: {
|
||||
status: 'failure',
|
||||
message: 'Unable to save page.',
|
||||
ctx: toolbarCtx,
|
||||
},
|
||||
})
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
return result.uploadFileRequest
|
||||
}
|
||||
|
||||
async function savePdfFile(
|
||||
currentTab,
|
||||
url,
|
||||
requestId,
|
||||
contentType,
|
||||
contentObjUrl
|
||||
) {
|
||||
const toolbarCtx = {
|
||||
omnivoreURL,
|
||||
originalURL: url,
|
||||
requestId: requestId,
|
||||
}
|
||||
completedRequests[toolbarCtx.requestId] = undefined
|
||||
|
||||
browserApi.tabs.sendMessage(currentTab.id, {
|
||||
action: ACTIONS.ShowToolbar,
|
||||
payload: {
|
||||
type: 'loading',
|
||||
ctx: toolbarCtx,
|
||||
},
|
||||
})
|
||||
const uploadRequestResult = await uploadFileRequest(url, contentType)
|
||||
console.log('done uploading pdf', uploadRequestResult)
|
||||
const uploadFileResult = await uploadFile(
|
||||
uploadRequestResult,
|
||||
contentType,
|
||||
contentObjUrl
|
||||
)
|
||||
console.log(' uploadFileResult: ', uploadFileResult)
|
||||
URL.revokeObjectURL(contentObjUrl)
|
||||
|
||||
if (uploadFileResult && uploadRequestResult.createdPageId) {
|
||||
completedRequests[toolbarCtx.requestId] = {
|
||||
requestId: toolbarCtx.requestId,
|
||||
responseId: uploadRequestResult.createdPageId,
|
||||
}
|
||||
|
||||
const data = JSON.stringify({
|
||||
query: `mutation UploadFileRequest($input: UploadFileRequestInput!) {
|
||||
uploadFileRequest(input:$input) {
|
||||
... on UploadFileRequestError {
|
||||
errorCodes
|
||||
}
|
||||
... on UploadFileRequestSuccess {
|
||||
id
|
||||
createdPageId
|
||||
uploadSignedUrl
|
||||
}
|
||||
}
|
||||
}`,
|
||||
variables: {
|
||||
input: {
|
||||
url,
|
||||
contentType,
|
||||
createPageEntry: true,
|
||||
browserApi.tabs.sendMessage(currentTab.id, {
|
||||
action: ACTIONS.UpdateStatus,
|
||||
payload: {
|
||||
status: 'success',
|
||||
target: 'page',
|
||||
ctx: {
|
||||
requestId: toolbarCtx.requestId,
|
||||
responseId: uploadRequestResult.createdPageId,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
xhr.open('POST', omnivoreGraphqlURL + 'graphql', true)
|
||||
setupConnection(xhr)
|
||||
|
||||
xhr.send(data)
|
||||
})
|
||||
return uploadFileResult
|
||||
}
|
||||
|
||||
function clearClickCompleteState() {
|
||||
@ -211,14 +203,6 @@ async function saveApiRequest(currentTab, query, field, input) {
|
||||
|
||||
try {
|
||||
const result = await gqlRequest(omnivoreGraphqlURL + 'graphql', requestBody)
|
||||
console.log(
|
||||
'result: ',
|
||||
field,
|
||||
result,
|
||||
result[field],
|
||||
result[field]['errorCodes']
|
||||
)
|
||||
|
||||
if (result[field]['errorCodes']) {
|
||||
if (result[field]['errorCodes'][0] === 'UNAUTHORIZED') {
|
||||
browserApi.tabs.sendMessage(currentTab.id, {
|
||||
@ -342,8 +326,6 @@ async function processPendingRequests(tabId) {
|
||||
}
|
||||
})
|
||||
|
||||
console.log('updated pending requests: ', pendingRequests)
|
||||
|
||||
// TODO: need to handle clearing completedRequests also
|
||||
}
|
||||
|
||||
@ -393,6 +375,7 @@ async function saveArticle(tab) {
|
||||
const uploadResult = await savePdfFile(
|
||||
tab,
|
||||
encodeURI(tab.url),
|
||||
requestId,
|
||||
pageInfo.contentType,
|
||||
uploadContentObjUrl
|
||||
)
|
||||
@ -759,7 +742,6 @@ function init() {
|
||||
|
||||
// forward messages from grab-iframe-content.js script to tabs
|
||||
browserApi.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
console.log(' MESSAGE', request.action, request)
|
||||
if (request.forwardToTab) {
|
||||
delete request.forwardToTab
|
||||
browserApi.tabs.sendRequest(sender.tab.id, request)
|
||||
@ -783,7 +765,6 @@ function init() {
|
||||
}
|
||||
|
||||
if (request.action === ACTIONS.SetLabels) {
|
||||
console.log('pushing setLabels: ', pendingRequests)
|
||||
pendingRequests.push({
|
||||
id: uuidv4(),
|
||||
type: 'SET_LABELS',
|
||||
|
||||
@ -80,26 +80,54 @@
|
||||
return root
|
||||
}
|
||||
|
||||
function createCtaModal(url) {
|
||||
const fragment = document.createDocumentFragment()
|
||||
async function createCtaModal(url) {
|
||||
if (currentToastEl) {
|
||||
currentToastEl.remove()
|
||||
currentToastEl = undefined
|
||||
}
|
||||
|
||||
const iframeEl = document.createElement('iframe')
|
||||
const iframePath = '/views/cta-popup.html?url=' + encodeURIComponent(url)
|
||||
const iframeUrl = ENV_EXTENSION_ORIGIN + iframePath
|
||||
iframeEl.src = iframeUrl
|
||||
iframeEl.style.cssText = `all: initial !important;
|
||||
width: 310px !important;
|
||||
height: 360px !important;
|
||||
`
|
||||
fragment.appendChild(iframeEl)
|
||||
document.body.appendChild(fragment)
|
||||
const file = await fetch(browserApi.runtime.getURL('/views/cta-popup.html'))
|
||||
const html = await file.text()
|
||||
|
||||
return fragment
|
||||
const root = document.createElement('div')
|
||||
root.attachShadow({ mode: 'open' })
|
||||
if (root.shadowRoot) {
|
||||
root.shadowRoot.innerHTML = `<style>:host {all initial;}</style>`
|
||||
}
|
||||
|
||||
const toastEl = document.createElement('div')
|
||||
toastEl.id = '#omnivore-toast'
|
||||
toastEl.innerHTML = html
|
||||
root.shadowRoot.appendChild(toastEl)
|
||||
|
||||
document.body.appendChild(root)
|
||||
connectButtons(root)
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
function displayLoggedOutView() {
|
||||
console.log('displaying logged out view')
|
||||
createCtaModal(ctx.omnivoreURL)
|
||||
cancelAutoDismiss()
|
||||
updatePageStatus('failure')
|
||||
toggleRow('#omnivore-logged-out-row')
|
||||
updateStatusBox(
|
||||
'#omnivore-logged-out-status',
|
||||
'empty',
|
||||
`You are not logged in.`
|
||||
)
|
||||
disableAllButtons()
|
||||
}
|
||||
|
||||
function disableAllButtons() {
|
||||
const actionButtons = [
|
||||
'#omnivore-toast-edit-title-btn',
|
||||
'#omnivore-toast-edit-labels-btn',
|
||||
'#omnivore-toast-read-now-btn',
|
||||
]
|
||||
actionButtons.forEach((btnId) => {
|
||||
const btn = currentToastEl.shadowRoot.querySelector(btnId)
|
||||
btn.disabled = true
|
||||
})
|
||||
}
|
||||
|
||||
function cancelAutoDismiss() {
|
||||
@ -116,37 +144,13 @@
|
||||
if (payload.ctx) {
|
||||
ctx = { ...ctx, ...payload.ctx }
|
||||
}
|
||||
console.log('updated ctx: ', ctx)
|
||||
|
||||
switch (payload.target) {
|
||||
case 'logged_out':
|
||||
displayLoggedOutView()
|
||||
break
|
||||
case 'page':
|
||||
{
|
||||
const statusBox = currentToastEl.shadowRoot.querySelector(
|
||||
'.omnivore-toast-statusBox'
|
||||
)
|
||||
switch (payload.status) {
|
||||
case 'loading':
|
||||
statusBox.innerHTML = systemIcons.animatedLoader
|
||||
break
|
||||
case 'success':
|
||||
// Auto hide if everything went well and the user
|
||||
// has not initiated any interaction.
|
||||
hideToastTimeout = setTimeout(function () {
|
||||
console.log('hiding: ', currentToastEl, doNotHide)
|
||||
if (!doNotHide) {
|
||||
currentToastEl.remove()
|
||||
currentToastEl = undefined
|
||||
}
|
||||
}, 2500)
|
||||
statusBox.innerHTML = systemIcons.success
|
||||
break
|
||||
case 'failure':
|
||||
statusBox.innerHTML = systemIcons.failure
|
||||
}
|
||||
}
|
||||
updatePageStatus(payload.status)
|
||||
break
|
||||
case 'title':
|
||||
updateStatusBox(
|
||||
@ -207,6 +211,31 @@
|
||||
})
|
||||
}
|
||||
|
||||
function updatePageStatus(status) {
|
||||
const statusBox = currentToastEl.shadowRoot.querySelector(
|
||||
'.omnivore-toast-statusBox'
|
||||
)
|
||||
switch (status) {
|
||||
case 'loading':
|
||||
statusBox.innerHTML = systemIcons.animatedLoader
|
||||
break
|
||||
case 'success':
|
||||
// Auto hide if everything went well and the user
|
||||
// has not initiated any interaction.
|
||||
hideToastTimeout = setTimeout(function () {
|
||||
console.log('hiding: ', currentToastEl, doNotHide)
|
||||
if (!doNotHide) {
|
||||
currentToastEl.remove()
|
||||
currentToastEl = undefined
|
||||
}
|
||||
}, 2500)
|
||||
statusBox.innerHTML = systemIcons.success
|
||||
break
|
||||
case 'failure':
|
||||
statusBox.innerHTML = systemIcons.failure
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatusBox(boxId, state, message, dismissAfter) {
|
||||
const statusBox = currentToastEl.shadowRoot.querySelector(boxId)
|
||||
const image = (() => {
|
||||
@ -217,6 +246,8 @@
|
||||
return systemIcons.success
|
||||
case 'failure':
|
||||
return systemIcons.failure
|
||||
case 'none':
|
||||
return ''
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
@ -224,7 +255,7 @@
|
||||
if (image) {
|
||||
statusBox.innerHTML = `<span style='padding-right: 10px'>${image}</span><span style='line-height: 20px'>${message}</span>`
|
||||
} else {
|
||||
statusBox.innerText = message
|
||||
statusBox.innerHTML = message
|
||||
}
|
||||
if (dismissAfter) {
|
||||
setTimeout(() => {
|
||||
@ -256,7 +287,8 @@
|
||||
{ id: '#omnivore-toast-edit-labels-btn', func: editLabels },
|
||||
{ id: '#omnivore-toast-read-now-btn', func: readNow },
|
||||
{ id: '#omnivore-open-menu-btn', func: openMenu },
|
||||
{ id: '#omnivore-toast-close-button', func: closeToast },
|
||||
{ id: '#omnivore-toast-close-btn', func: closeToast },
|
||||
{ id: '#omnivore-toast-login-btn', func: login },
|
||||
]
|
||||
|
||||
for (const btnInfo of btns) {
|
||||
@ -522,6 +554,11 @@
|
||||
currentToastEl = undefined
|
||||
}
|
||||
|
||||
function login() {
|
||||
window.open(new URL(`/login`, ctx.omnivoreURL), '_blank')
|
||||
setTimeout(closeToast, 2000)
|
||||
}
|
||||
|
||||
window.showToolbar = showToolbar
|
||||
window.updateStatus = updateStatus
|
||||
window.updateLabelsFromCache = updateLabelsFromCache
|
||||
|
||||
@ -7,7 +7,102 @@
|
||||
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" href="/styles/cta-popup.css">
|
||||
<style>
|
||||
@font-face {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-family: Inter;
|
||||
src:
|
||||
local(""),
|
||||
url("/fonts/inter-v3-latin-400.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-family: Inter;
|
||||
src:
|
||||
local(""),
|
||||
url("/fonts/inter-v3-latin-600.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-family: Inter;
|
||||
src:
|
||||
local(""),
|
||||
url("/fonts/inter-v3-latin-700.woff2") format("woff2");
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cta-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 24px 12px;
|
||||
background: linear-gradient(150deg, #fff 55%, #ffde8c 55%);
|
||||
background-color: #fff;
|
||||
color: #3d3d3d;
|
||||
font-family: Inter, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cta-container__wrapper {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.cta-container__title {
|
||||
padding: 0 24px 24px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.cta-container__icon {
|
||||
vertical-align: unset;
|
||||
margin-bottom: -4px;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.cta-container__link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 184px;
|
||||
height: 34px;
|
||||
margin: 36px auto 14px;
|
||||
border-radius: 40px;
|
||||
background-color: #fff;
|
||||
color: inherit;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cta-container__text {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cta-container__textlink {
|
||||
color: inherit;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cta-container">
|
||||
|
||||
@ -30,6 +30,12 @@
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
#omnivore-toast-container #omnivore-logged-out-row {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#omnivore-toast-container .omnivore-toast-func-row[data-state="open"] {
|
||||
display: flex;
|
||||
}
|
||||
@ -77,13 +83,13 @@
|
||||
#omnivore-toast-container .omnivore-save-button button {
|
||||
background-color: rgb(255, 210, 52)
|
||||
}
|
||||
#omnivore-toast-container #omnivore-toast-close-button:hover {
|
||||
#omnivore-toast-container #omnivore-toast-close-btn:hover {
|
||||
background-color: unset;
|
||||
}
|
||||
#omnivore-toast-container #omnivore-toast-close-button svg {
|
||||
#omnivore-toast-container #omnivore-toast-close-btn svg {
|
||||
fill: #3D3D3D;
|
||||
}
|
||||
#omnivore-toast-container #omnivore-toast-close-button svg:hover {
|
||||
#omnivore-toast-container #omnivore-toast-close-btn svg:hover {
|
||||
fill: #D9D9D9;
|
||||
stroke: white;
|
||||
}
|
||||
@ -275,7 +281,7 @@
|
||||
</button>
|
||||
<span class="omnivore-toast-divider"></span>
|
||||
-->
|
||||
<button id="omnivore-toast-close-button">
|
||||
<button id="omnivore-toast-close-btn">
|
||||
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="9.50049" cy="9.52783" r="9" />
|
||||
<path d="M12.554 11.874L12.554 11.874L10.2075 9.52783L12.554 7.18165L12.554 7.18164C12.6478 7.08785 12.7005 6.96063 12.7005 6.82798C12.7005 6.69533 12.6478 6.56812 12.554 6.47432C12.4602 6.38053 12.333 6.32783 12.2003 6.32783C12.0677 6.32783 11.9405 6.38053 11.8467 6.47432L11.8467 6.47433L9.50049 8.82087L7.15431 6.47433L7.1543 6.47432C7.0605 6.38053 6.93329 6.32783 6.80064 6.32783C6.66799 6.32783 6.54078 6.38053 6.44698 6.47432C6.35318 6.56812 6.30049 6.69533 6.30049 6.82798C6.30049 6.96063 6.35318 7.08785 6.44698 7.18164L6.44699 7.18165L8.79352 9.52783L6.44699 11.874L6.44698 11.874C6.35318 11.9678 6.30049 12.095 6.30049 12.2277C6.30049 12.3603 6.35318 12.4875 6.44698 12.5813C6.54078 12.6751 6.66799 12.7278 6.80064 12.7278C6.93329 12.7278 7.0605 12.6751 7.1543 12.5813L7.15431 12.5813L9.50049 10.2348L11.8467 12.5813L11.8467 12.5813C11.8931 12.6278 11.9483 12.6646 12.0089 12.6898C12.0696 12.7149 12.1347 12.7278 12.2003 12.7278C12.266 12.7278 12.3311 12.7149 12.3917 12.6898C12.4524 12.6646 12.5076 12.6278 12.554 12.5813C12.6004 12.5349 12.6373 12.4798 12.6624 12.4191C12.6876 12.3584 12.7005 12.2934 12.7005 12.2277C12.7005 12.162 12.6876 12.097 12.6624 12.0363C12.6373 11.9756 12.6004 11.9205 12.554 11.874Z" fill="#EBEBEB" stroke="#EBEBEB" stroke-width="0.4"/>
|
||||
@ -322,6 +328,10 @@
|
||||
</svg>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="omnivore-logged-out-row" class="omnivore-toast-func-row" data-state="closed">
|
||||
<span id="omnivore-logged-out-status" class="omnivore-toast-func-status"></span>
|
||||
<a href="" id="omnivore-toast-login-btn">Login to Omnivore</a>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user