Export button

main
Elbert Alias 3 years ago
parent 2d0ada890a
commit 3e8869ac47

@ -72,7 +72,7 @@ p {
text-align: right; text-align: right;
} }
.button__link:hover:before { .button__link:before {
background: var(--color-primary); background: var(--color-primary);
border-radius: 4px; border-radius: 4px;
content: ''; content: '';
@ -81,6 +81,10 @@ p {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
opacity: 0;
}
.button__link:hover:before {
opacity: .1; opacity: .1;
} }
@ -223,16 +227,22 @@ small {
overflow: hidden; overflow: hidden;
} }
.tab-item:nth-child(2) {
min-width: 500px;
}
.tab-item--hidden { .tab-item--hidden {
display: none; display: none;
} }
.credits { .credits {
background: var(--color-secondary);
color: var(--color-text-lighten); color: var(--color-text-lighten);
display: block; display: block;
font-size: .8rem;
text-align: right; text-align: right;
flex: 1; flex: 1;
padding: 0 1.5rem; padding: 1rem 1.5rem 0 1.5rem;
margin-bottom: -3px; margin-bottom: -3px;
line-height: 1rem; line-height: 1rem;
} }
@ -393,13 +403,22 @@ small {
} }
.plus-download { .plus-download {
background: var(--color-secondary); flex: 1 0;
padding: 1rem 1.5rem 0 1.5rem;
text-align: right; text-align: right;
padding: 0 1.5rem;
white-space: nowrap;
}
.plus-download .button__link:before {
opacity: .1;
}
.plus-download .button__link:hover:before {
opacity: .2;
} }
.plus-download--hidden { .plus-download--hidden {
display: none; visibility: hidden;
} }
.plus-error { .plus-error {
@ -849,7 +868,7 @@ body.dynamic-icon .category__heading:hover .category__pin {
color: var(--color-text-dark); color: var(--color-text-dark);
} }
.dark .button__link:hover:before { .dark .button__link:before {
background: white; background: white;
} }
@ -953,6 +972,11 @@ body.dynamic-icon .category__heading:hover .category__pin {
border-color: var(--color-primary); border-color: var(--color-primary);
} }
.dark .credits {
background: var(--color-primary-darken);
}
.dark .footer { .dark .footer {
background: var(--color-primary-darken); background: var(--color-primary-darken);
border-top: 1px solid var(--color-primary); border-top: 1px solid var(--color-primary);

@ -49,13 +49,22 @@
<div class="tab tab--technologies tab--active" data-i18n="tabTechnologies">&nbsp;</div> <div class="tab tab--technologies tab--active" data-i18n="tabTechnologies">&nbsp;</div>
<div class="tab tab--plus"><span data-i18n="tabPlus">&nbsp;</span></div> <div class="tab tab--plus"><span data-i18n="tabPlus">&nbsp;</span></div>
<div class="credits credits--hidden"> <div class="plus-download plus-download--hidden">
<span data-i18n="creditBalance">&nbsp;</span> <div class="plus-download__button button">
<span class="button__link">
<svg class="button__icon button__icon--left" viewBox="0 0 24 24">
<path fill="currentColor" d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" />
</svg>
<span class="credits__remaining">&nbsp;</span> <span class="button__text">
Export
</span>
</span>
</div>
</div> </div>
</div> </div>
<div class="tab-items">
<div class="tab-item"> <div class="tab-item">
<div class="empty empty--hidden"> <div class="empty empty--hidden">
<div class="empty__text" data-i18n="noAppsDetected">&nbsp;</div> <div class="empty__text" data-i18n="noAppsDetected">&nbsp;</div>
@ -154,18 +163,10 @@
</div> </div>
<div class="tab-item tab-item--hidden"> <div class="tab-item tab-item--hidden">
<div class="plus-download plus-download--hidden"> <div class="credits credits--hidden">
<div class="plus-download__button button"> <span data-i18n="creditBalance">&nbsp;</span>
<span class="button__link">
<svg class="button__icon button__icon--left" viewBox="0 0 24 24">
<path fill="currentColor" d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" />
</svg>
<span class="button__text"> <span class="credits__remaining">&nbsp;</span>
Download CSV
</span>
</span>
</div>
</div> </div>
<div class="plus-error plus-error--hidden"> <div class="plus-error plus-error--hidden">
@ -231,6 +232,7 @@
<div class="plus-empty plus-empty--hidden" data-i18n="plusEmpty"></div> <div class="plus-empty plus-empty--hidden" data-i18n="plusEmpty"></div>
<div class="plus-crawl plus-crawl--hidden" data-i18n="plusCrawl"></div> <div class="plus-crawl plus-crawl--hidden" data-i18n="plusCrawl"></div>
</div> </div>
</div>
<div class="footer"> <div class="footer">
<div class="footer__heading"> <div class="footer__heading">

@ -8,9 +8,6 @@ const { agent, open, i18n, getOption, setOption, promisify, sendMessage } =
const baseUrl = 'https://www.wappalyzer.com' const baseUrl = 'https://www.wappalyzer.com'
const utm = '?utm_source=popup&utm_medium=extension&utm_campaign=wappalyzer' const utm = '?utm_source=popup&utm_medium=extension&utm_campaign=wappalyzer'
let csv = ''
let csvFilename = ''
const footers = [ const footers = [
{ {
heading: 'Generate sales leads', heading: 'Generate sales leads',
@ -107,6 +104,71 @@ function setDisabledDomain(enabled) {
} }
} }
function getCsv() {
let hostname = ''
let www = false
let https = false
try {
let protocol = ''
;({ hostname, protocol } = new URL(Popup.cache.url))
www = hostname.startsWith('www')
https = protocol === 'https:'
hostname = hostname.replace(/^www\./, '')
} catch (error) {
// Continue
}
const columns = [
'URL',
...Popup.cache.categories.map(({ id }) =>
chrome.i18n.getMessage(`categoryName${id}`)
),
...attributeKeys.map((key) =>
chrome.i18n.getMessage(
`attribute${
key.charAt(0).toUpperCase() + key.slice(1).replace('.', '_')
}`
)
),
]
const csv = [`"${columns.join('","')}"`]
const filename = `wappalyzer${
hostname ? `_${hostname.replace('.', '-')}` : ''
}.csv`
const row = [`http${https ? 's' : ''}://${www ? 'www.' : ''}${hostname}`]
row.push(
...Popup.cache.categories.reduce((categories, { id }) => {
categories.push(
Popup.cache.detections
.filter(({ categories }) =>
categories.some(({ id: _id }) => _id === id)
)
.map(({ name }) => name)
.join(' ; ')
)
return categories
}, [])
)
row.push(
...attributeKeys.map((key) => csvEscape(Popup.cache.attributeValues[key]))
)
csv.push(`"${row.join('","')}"`)
return { csv, filename }
}
function csvEscape(value = '') { function csvEscape(value = '') {
if (Array.isArray(value)) { if (Array.isArray(value)) {
value = value value = value
@ -136,6 +198,13 @@ const Popup = {
* Initialise popup * Initialise popup
*/ */
async init() { async init() {
Popup.cache = {
url: '',
categories: [],
detections: [],
attributeValues: {},
}
const el = { const el = {
body: document.body, body: document.body,
terms: document.querySelector('.terms'), terms: document.querySelector('.terms'),
@ -150,6 +219,7 @@ const Popup = {
headerSwitchDisabled: document.querySelector('.header__switch--disabled'), headerSwitchDisabled: document.querySelector('.header__switch--disabled'),
plusConfigureApiKey: document.querySelector('.plus-configure__apikey'), plusConfigureApiKey: document.querySelector('.plus-configure__apikey'),
plusConfigureSave: document.querySelector('.plus-configure__save'), plusConfigureSave: document.querySelector('.plus-configure__save'),
plusDownload: document.querySelector('.plus-download'),
plusDownloadLink: document.querySelector( plusDownloadLink: document.querySelector(
'.plus-download__button .button__link' '.plus-download__button .button__link'
), ),
@ -246,6 +316,8 @@ const Popup = {
;[{ url }] = tabs ;[{ url }] = tabs
if (url.startsWith('http')) { if (url.startsWith('http')) {
Popup.cache.url = url
const { hostname } = new URL(url) const { hostname } = new URL(url)
setDisabledDomain(disabledDomains.includes(hostname)) setDisabledDomain(disabledDomains.includes(hostname))
@ -322,6 +394,7 @@ const Popup = {
el.tabItems[index].classList.remove('tab-item--hidden') el.tabItems[index].classList.remove('tab-item--hidden')
el.credits.classList.add('credits--hidden') el.credits.classList.add('credits--hidden')
el.plusDownload.classList.remove('plus-download--hidden')
el.footer.classList.remove('footer--hidden') el.footer.classList.remove('footer--hidden')
if (tab.classList.contains('tab--plus')) { if (tab.classList.contains('tab--plus')) {
@ -331,7 +404,7 @@ const Popup = {
}) })
// Download // Download
el.plusDownloadLink.addEventListener('click', (event) => Popup.downloadCsv) el.plusDownloadLink.addEventListener('click', Popup.downloadCsv)
// Footer // Footer
const item = const item =
@ -381,6 +454,8 @@ const Popup = {
// Apply internationalization // Apply internationalization
i18n() i18n()
Popup.cache.categories = await Popup.driver('getCategories')
}, },
driver(func, args) { driver(func, args) {
@ -423,9 +498,12 @@ const Popup = {
* @param {Array} detections * @param {Array} detections
*/ */
async onGetDetections(detections = []) { async onGetDetections(detections = []) {
Popup.cache.detections = detections
const el = { const el = {
empty: document.querySelector('.empty'), empty: document.querySelector('.empty'),
detections: document.querySelector('.detections'), detections: document.querySelector('.detections'),
plusDownload: document.querySelector('.plus-download'),
} }
detections = (detections || []) detections = (detections || [])
@ -435,12 +513,14 @@ const Popup = {
if (!detections || !detections.length) { if (!detections || !detections.length) {
el.empty.classList.remove('empty--hidden') el.empty.classList.remove('empty--hidden')
el.detections.classList.add('detections--hidden') el.detections.classList.add('detections--hidden')
el.plusDownload.classList.add('plus-download--hidden')
return return
} }
el.empty.classList.add('empty--hidden') el.empty.classList.add('empty--hidden')
el.detections.classList.remove('detections--hidden') el.detections.classList.remove('detections--hidden')
el.plusDownload.classList.remove('plus-download--hidden')
while (el.detections.firstChild) { while (el.detections.firstChild) {
el.detections.removeChild(detections.firstChild) el.detections.removeChild(detections.firstChild)
@ -554,9 +634,6 @@ const Popup = {
crawl: document.querySelector('.plus-crawl'), crawl: document.querySelector('.plus-crawl'),
error: document.querySelector('.plus-error'), error: document.querySelector('.plus-error'),
download: document.querySelector('.plus-download'), download: document.querySelector('.plus-download'),
downloadLink: document.querySelector(
'.plus-download__button .button__link'
),
errorMessage: document.querySelector('.plus-error__message'), errorMessage: document.querySelector('.plus-error__message'),
configure: document.querySelector('.plus-configure'), configure: document.querySelector('.plus-configure'),
credits: document.querySelector('.credits'), credits: document.querySelector('.credits'),
@ -565,6 +642,7 @@ const Popup = {
} }
el.error.classList.add('plus-error--hidden') el.error.classList.add('plus-error--hidden')
el.download.classList.add('plus-download--hidden')
if (apiKey) { if (apiKey) {
el.loading.classList.remove('loading--hidden') el.loading.classList.remove('loading--hidden')
@ -579,7 +657,6 @@ const Popup = {
} }
el.panels.classList.add('panels--hidden') el.panels.classList.add('panels--hidden')
el.download.classList.add('plus-download--hidden')
el.empty.classList.add('plus-empty--hidden') el.empty.classList.add('plus-empty--hidden')
el.crawl.classList.add('plus-crawl--hidden') el.crawl.classList.add('plus-crawl--hidden')
el.error.classList.add('plus-error--hidden') el.error.classList.add('plus-error--hidden')
@ -588,24 +665,6 @@ const Popup = {
el.panels.removeChild(el.panels.lastElementChild) el.panels.removeChild(el.panels.lastElementChild)
} }
let hostname = ''
let www = false
let https = false
try {
let protocol = ''
;({ hostname, protocol } = new URL(url))
www = hostname.startsWith('www')
https = protocol === 'https:'
hostname = hostname.replace(/^www\./, '')
} catch (error) {
// Continue
}
try { try {
const response = await fetch( const response = await fetch(
`https://api.wappalyzer.com/v2/plus/${encodeURIComponent(url)}`, `https://api.wappalyzer.com/v2/plus/${encodeURIComponent(url)}`,
@ -635,9 +694,8 @@ const Popup = {
10 10
).toLocaleString() ).toLocaleString()
el.credits.classList.remove('credits--hidden')
el.loading.classList.add('loading--hidden') el.loading.classList.add('loading--hidden')
el.credits.classList.remove('credits--hidden')
if (crawl) { if (crawl) {
document document
@ -649,50 +707,11 @@ const Popup = {
if (!Object.keys(attributes).length) { if (!Object.keys(attributes).length) {
el.empty.classList.remove('plus-empty--hidden') el.empty.classList.remove('plus-empty--hidden')
el.download.classList.remove('plus-download--hidden')
return return
} }
const categories = await Popup.driver('getCategories')
const columns = [
'URL',
...categories.map(({ id }) =>
chrome.i18n.getMessage(`categoryName${id}`)
),
...attributeKeys.map((key) =>
chrome.i18n.getMessage(
`attribute${
key.charAt(0).toUpperCase() + key.slice(1).replace('.', '_')
}`
)
),
]
csv = [`"${columns.join('","')}"`]
csvFilename = `wappalyzer${
hostname ? `_${hostname.replace('.', '-')}` : ''
}.csv`
const row = [`http${https ? 's' : ''}://${www ? 'www.' : ''}${hostname}`]
const detections = await Popup.driver('getDetections')
row.push(
...categories.reduce((categories, { id }) => {
categories.push(
detections
.filter(({ categories }) =>
categories.some(({ id: _id }) => _id === id)
)
.map(({ name }) => name)
.join(' ; ')
)
return categories
}, [])
)
const attributeValues = {} const attributeValues = {}
Object.keys(attributes).forEach((set) => { Object.keys(attributes).forEach((set) => {
@ -800,26 +819,7 @@ const Popup = {
el.panels.appendChild(panel) el.panels.appendChild(panel)
}) })
row.push(...attributeKeys.map((key) => csvEscape(attributeValues[key]))) Popup.cache.attributeValues = attributeValues
csv.push(`"${row.join('","')}"`)
el.downloadLink.addEventListener('click', (event) => {
event.preventDefault()
const file = URL.createObjectURL(
new Blob([csv.join('\n')], { type: 'text/csv;charset=utf-8' })
)
chrome.downloads.download({
url: file,
filename: `wappalyzer${
hostname ? `_${hostname.replace('.', '-')}` : ''
}.csv`,
})
return false
})
el.panels.classList.remove('panels--hidden') el.panels.classList.remove('panels--hidden')
el.download.classList.remove('plus-download--hidden') el.download.classList.remove('plus-download--hidden')
@ -869,14 +869,20 @@ const Popup = {
i18n() i18n()
}, },
downloadCsv() { downloadCsv(event) {
console.log('x')
event.preventDefault() event.preventDefault()
chrome.downloads.download({ const { csv, filename } = getCsv()
url: URL.createObjectURL(
const file = URL.createObjectURL(
new Blob([csv.join('\n')], { type: 'text/csv;charset=utf-8' }) new Blob([csv.join('\n')], { type: 'text/csv;charset=utf-8' })
), )
filename: csvFilename,
chrome.downloads.download({
url: file,
filename,
}) })
return false return false