Skip to content

Commit

Permalink
feat: ✨ share buttons
Browse files Browse the repository at this point in the history
  • Loading branch information
krthr committed Sep 25, 2024
1 parent 6af5a25 commit 9c458fe
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 6 deletions.
1 change: 1 addition & 0 deletions adonisrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default defineConfig({
() => import('@adonisjs/static/static_provider'),
() => import('@adonisjs/lucid/database_provider'),
() => import('@adonisjs/drive/drive_provider'),
() => import('#providers/app_provider'),
],

/*
Expand Down
3 changes: 1 addition & 2 deletions app/controllers/poems_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import type { HttpContext } from '@adonisjs/core/http'
import { inject } from '@adonisjs/core'

import Poem from '#models/poem'
import { poemStoreValidator } from '#validators/poem_store'

import PoemService from '#services/poem_service'
import { poemStoreValidator } from '#validators/poem_store'

export default class PoemsController {
/**
Expand Down
183 changes: 183 additions & 0 deletions app/services/device_service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/**
* Taken from https://github.com/nuxt-modules/device
*/

import { Request } from '@adonisjs/core/http'

export type Device = {
userAgent?: string
isDesktop: boolean
isIos: boolean
isAndroid: boolean
isMobile: boolean
isMobileOrTablet: boolean
isDesktopOrTablet: boolean
isTablet: boolean
isWindows?: boolean
isMacOS?: boolean
isApple: boolean
isSafari?: boolean
isFirefox?: boolean
isEdge?: boolean
isChrome?: boolean
isSamsung?: boolean
}

const REGEX_MOBILE1 =
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|FBAN|FBAV|fennec|hiptop|iemobile|ip(hone|od)|Instagram|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i

const REGEX_MOBILE2 =
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i

const REGEX_MOBILE_OR_TABLET1 =
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|FBAN|FBAV|fennec|hiptop|iemobile|ip(hone|od)|Instagram|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i

const REGEX_MOBILE_OR_TABLET2 =
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i

function isMobile(userAgent: string): boolean {
return REGEX_MOBILE1.test(userAgent) || REGEX_MOBILE2.test(userAgent.slice(0, 4))
}

function isMobileOrTablet(userAgent: string): boolean {
return (
REGEX_MOBILE_OR_TABLET1.test(userAgent) || REGEX_MOBILE_OR_TABLET2.test(userAgent.slice(0, 4))
)
}

function isIos(userAgent: string): boolean {
return /iPad|iPhone|iPod/.test(userAgent)
}

function isAndroid(userAgent: string): boolean {
return /android/i.test(userAgent)
}

function isWindows(userAgent: string): boolean {
return /Windows/.test(userAgent)
}

function isMacOS(userAgent: string): boolean {
return /Mac OS X/.test(userAgent)
}

// Following regular expressions are originated from bowser(https://github.com/lancedikson/bowser).
// Copyright 2015, Dustin Diaz (the "Original Author")
// https://github.com/lancedikson/bowser/blob/master/LICENSE
const browsers = [
{ name: 'Samsung', regex: /SamsungBrowser/i },
{ name: 'Edge', regex: /edg(?:[ea]|ios)?\//i },
{ name: 'Firefox', regex: /firefox|iceweasel|fxios/i },
{ name: 'Chrome', regex: /chrome|crios|crmo/i },
{ name: 'Safari', regex: /safari|applewebkit/i },
]

function getBrowserName(userAgent: string): string {
for (const browser of browsers) {
if (browser.regex.test(userAgent)) {
return browser.name
}
}

return ''
}

export default class DeviceService {
public static getDevice(request: Request): Device {
const userAgent = request.header('user-agent')
const headers = request.headers()

let mobile = false
let mobileOrTablet = false
let ios = false
let android = false

if (userAgent === 'Amazon CloudFront') {
if (headers['cloudfront-is-mobile-viewer'] === 'true') {
mobile = true
mobileOrTablet = true
}

if (headers['cloudfront-is-tablet-viewer'] === 'true') {
mobile = false
mobileOrTablet = true
}

if (headers['cloudfront-is-desktop-viewer'] === 'true') {
mobile = false
mobileOrTablet = false
}

if (headers['cloudfront-is-ios-viewer'] === 'true') {
ios = true
}

if (headers['cloudfront-is-android-viewer'] === 'true') {
android = true
}
} else if (headers && headers['cf-device-type']) {
// Cloudflare
switch (headers['cf-device-type']) {
case 'mobile':
mobile = true
mobileOrTablet = true

break
case 'tablet':
mobile = false
mobileOrTablet = true

break
case 'desktop':
mobile = false
mobileOrTablet = false

break
}
} else if (userAgent) {
mobile = isMobile(userAgent)
mobileOrTablet = isMobileOrTablet(userAgent)
ios = isIos(userAgent)
android = isAndroid(userAgent)
}

let windows: boolean | undefined
let macOS: boolean | undefined
let isSafari: boolean | undefined
let isFirefox: boolean | undefined
let isEdge: boolean | undefined
let isChrome: boolean | undefined
let isSamsung: boolean | undefined

if (userAgent) {
windows = isWindows(userAgent)
macOS = isMacOS(userAgent)
const browserName = getBrowserName(userAgent)

isSafari = browserName === 'Safari'
isFirefox = browserName === 'Firefox'
isEdge = browserName === 'Edge'
isChrome = browserName === 'Chrome'
isSamsung = browserName === 'Samsung'
}

return {
userAgent,
isMobile: mobile,
isMobileOrTablet: mobileOrTablet,
isTablet: !mobile && mobileOrTablet,
isDesktop: !mobileOrTablet,
isIos: ios,
isAndroid: android,
isWindows: windows,
isMacOS: macOS,
isApple: macOS || ios,
isDesktopOrTablet: !mobile,
isSafari,
isFirefox,
isEdge,
isChrome,
isSamsung,
}
}
}
2 changes: 1 addition & 1 deletion config/static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const staticServerConfig = defineConfig({
lastModified: true,
dotFiles: 'ignore',
cacheControl: true,
maxAge: '24 hours'
maxAge: '24 hours',
})

export default staticServerConfig
32 changes: 32 additions & 0 deletions providers/app_provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { ApplicationService } from '@adonisjs/core/types'

export default class AppProvider {
constructor(protected app: ApplicationService) {}

/**
* Register bindings to the container
*/
register() {}

/**
* The container bindings have booted
*/
async boot() {
await import('../src/extensions.js')
}

/**
* The application has been booted
*/
async start() {}

/**
* The process has been started
*/
async ready() {}

/**
* Preparing to shutdown the app
*/
async shutdown() {}
}
26 changes: 24 additions & 2 deletions resources/js/poem.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,32 @@ window.onload = () => {

await generateAndDownloadImage(poem, title)

window.gtag && window.gtag('event', 'share')
window.LogRocket && window.LogRocket.track('share')
window.gtag && window.gtag('event', 'download_image')
window.LogRocket && window.LogRocket.track('download_image')

bownloadBtn.removeAttribute('disabled')
bownloadBtn.classList.remove('loading')
}

const shareBtn = document.querySelector('#share-btn')

if ('share' in navigator) {
console.log('Can share!')

shareBtn.onclick = async () => {
const payload = {
title: window.poem.title,
text: window.poem.poem,
url: window.poem.url,
}

console.log('Sharing', payload)
await navigator.share(payload)

window.gtag && window.gtag('event', 'share_native')
window.LogRocket && window.LogRocket.track('share_native')
}

shareBtn.classList.remove('hidden')
}
}
34 changes: 34 additions & 0 deletions resources/views/components/poem/share.edge
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div id="share" class="mb-6 gap-x-4 flex flex-wrap gap-2 justify-center">
<a
class="btn btn-circle"
href="https://www.facebook.com/sharer/sharer.php?u={{ request.completeUrl() }}"
target="_blank"
>
<iconify-icon icon="ph:facebook-logo" width="30">
</iconify-icon>
</a>
<a
class="btn btn-circle"
href="https://wa.me/?text={{ poem.title }} {{ request.completeUrl() }}"
target="_blank"
>
<iconify-icon icon="ph:whatsapp-logo" width="30">
</iconify-icon>
</a>
<a
class="btn btn-circle"
href="https://twitter.com/share?url={{ request.completeUrl() }}&text={{ poem.title }}"
target="_blank"
>
<iconify-icon icon="ph:twitter-logo" width="30">
</iconify-icon>
</a>

<button id="share-btn" class="btn btn-neutral gap-2 hidden">
<iconify-icon icon="ph:share" width="30">
</iconify-icon>

Compartir
</button>
</div>

14 changes: 13 additions & 1 deletion resources/views/pages/poem.edge
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
description: truncate(poem.poem, 160),
image: 'https://poesia.pics' + image
})
@slot('meta')
<script>
window.poem = {
title: "{{ poem.title }}",
poem: `{{ poem.poem }}`,
url: "{{ request.completeUrl() }}"
};
</script>
@end

<div class="w-full">
<div id="poem">
<img
Expand Down Expand Up @@ -48,8 +58,10 @@
<span class="font-bold">#PoesiaPics</span>
</h2>

@!poem.share({ device, poem })

<div>
<button class="btn btn-ghost btn-outline gap-2" id="download-poem">
<button class="btn btn-primary gap-2" id="download-poem">
<iconify-icon icon="ph:download" width="25">
</iconify-icon>
Descargar poema
Expand Down
16 changes: 16 additions & 0 deletions src/extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Request } from '@adonisjs/core/http'

import type { Device } from '#services/device_service'
import DeviceService from '#services/device_service'

declare module '@adonisjs/core/http' {
export interface Request {
device?: Device
}
}

Request.getter('device', function (this: Request) {
try {
return DeviceService.getDevice(this)
} catch (error) {}
})

0 comments on commit 9c458fe

Please sign in to comment.