Merge pull request #1030 from mikecao/dev

v1.28.0
This commit is contained in:
Mike Cao 2022-03-17 21:26:11 -07:00 committed by GitHub
commit 2b4ddb5388
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
93 changed files with 1092 additions and 1399 deletions

View File

@ -16,7 +16,8 @@
"rules": {
"react/display-name": "off",
"react/react-in-jsx-scope": "off",
"react/prop-types": "off"
"react/prop-types": "off",
"import/no-anonymous-default-export": "off"
},
"globals": {
"React": "writable"

View File

@ -11,6 +11,7 @@ function MenuButton({
value,
options,
buttonClassName,
buttonVariant,
menuClassName,
menuPosition = 'bottom',
menuAlign = 'right',
@ -43,7 +44,7 @@ function MenuButton({
icon={icon}
className={classNames(styles.button, buttonClassName, { [styles.open]: showMenu })}
onClick={toggleMenu}
variant="light"
variant={buttonVariant}
>
{!hideLabel && (
<div className={styles.text}>{renderValue ? renderValue(selectedOption) : value}</div>

View File

@ -1,3 +1,4 @@
import classNames from 'classnames';
import Link from './Link';
import Button from './Button';
import XMark from 'assets/xmark.svg';
@ -5,9 +6,9 @@ import styles from './MobileMenu.module.css';
export default function MobileMenu({ items = [], onClose }) {
return (
<div className={styles.menu}>
<div className={classNames(styles.menu, 'container')}>
<div className={styles.header}>
<Button className={styles.button} icon={<XMark />} onClick={onClose} />
<Button icon={<XMark />} onClick={onClose} />
</div>
<div className={styles.items}>
{items.map(({ label, value }) => (

View File

@ -32,13 +32,10 @@
margin-top: 60px;
}
.button {
margin-right: 15px;
}
.header {
display: flex;
justify-content: flex-end;
align-items: center;
height: 100px;
padding: 0 30px;
}

View File

@ -34,7 +34,7 @@ export default function UpdateNotice() {
values={{ version: `v${latest}` }}
/>
</div>
<ButtonLayout>
<ButtonLayout className={styles.buttons}>
<Button size="xsmall" variant="action" onClick={handleViewClick}>
<FormattedMessage id="label.view-details" defaultMessage="View details" />
</Button>

View File

@ -2,12 +2,17 @@
display: flex;
justify-content: center;
align-items: center;
padding-top: 10px;
font-size: var(--font-size-small);
font-weight: 600;
padding-top: 20px;
}
.message {
font-size: var(--font-size-small);
font-weight: 600;
flex: 1;
text-align: center;
margin-right: 20px;
}
.buttons {
flex: 0;
}

View File

@ -1,3 +1,4 @@
import { useRouter } from 'next/router';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import Link from 'components/common/Link';
@ -14,6 +15,7 @@ import { HOMEPAGE_URL } from 'lib/constants';
export default function Header() {
const { user } = useUser();
const { pathname } = useRouter();
return (
<>
@ -21,7 +23,7 @@ export default function Header() {
<header className={classNames(styles.header, 'row')}>
<div className={styles.title}>
<Icon icon={<Logo />} size="large" className={styles.logo} />
<Link href={user ? '/' : HOMEPAGE_URL}>umami</Link>
<Link href={pathname.includes('/share') ? HOMEPAGE_URL : '/'}>umami</Link>
</div>
<HamburgerButton />
{user && (

View File

@ -6,6 +6,7 @@
}
.title {
flex: 1;
font-size: var(--font-size-large);
display: flex;
align-items: center;
@ -17,7 +18,7 @@
}
.links {
flex: 1;
flex: 2;
display: flex;
justify-content: center;
align-items: center;
@ -30,6 +31,7 @@
}
.buttons {
flex: 1;
display: flex;
justify-content: flex-end;
align-items: center;
@ -39,6 +41,7 @@
.header .buttons {
flex: 1;
}
.links {
order: 2;
margin: 20px 0;
@ -48,7 +51,7 @@
@media only screen and (max-width: 768px) {
.header {
padding: 0 15px;
padding: 0 30px;
}
.buttons,

View File

@ -14,9 +14,7 @@ export default function Layout({ title, children, header = true, footer = true }
</Head>
{header && <Header />}
<main className="container" dir={dir}>
{children}
</main>
<main>{children}</main>
{footer && <Footer />}
<div id="__modals" dir={dir} />
</>

View File

@ -9,7 +9,8 @@ import styles from './ActiveUsers.module.css';
export default function ActiveUsers({ websiteId, className, value, interval = 60000 }) {
const shareToken = useShareToken();
const { data } = useFetch(!value && `/website/${websiteId}/active`, {
const url = value !== undefined && websiteId ? `/website/${websiteId}/active` : null;
const { data } = useFetch(url, {
interval,
headers: { [TOKEN_HEADER]: shareToken?.token },
});

View File

@ -10,10 +10,11 @@ import useShareToken from 'hooks/useShareToken';
import { EVENT_COLORS, TOKEN_HEADER } from 'lib/constants';
export default function EventsChart({ websiteId, className, token }) {
const [dateRange] = useDateRange(websiteId);
const { startDate, endDate, unit, modified } = dateRange;
const [{ startDate, endDate, unit, modified }] = useDateRange(websiteId);
const [timezone] = useTimezone();
const { query } = usePageQuery();
const {
query: { url, eventType },
} = usePageQuery();
const shareToken = useShareToken();
const { data, loading } = useFetch(
@ -24,12 +25,13 @@ export default function EventsChart({ websiteId, className, token }) {
end_at: +endDate,
unit,
tz: timezone,
url: query.url,
url,
event_type: eventType,
token,
},
headers: { [TOKEN_HEADER]: shareToken?.token },
},
[modified],
[modified, eventType],
);
const datasets = useMemo(() => {

View File

@ -4,16 +4,18 @@ import MetricsTable from './MetricsTable';
import Tag from 'components/common/Tag';
import DropDown from 'components/common/DropDown';
import { eventTypeFilter } from 'lib/filters';
import usePageQuery from 'hooks/usePageQuery';
import styles from './EventsTable.module.css';
const EVENT_FILTER_DEFAULT = {
value: 'EVENT_FILTER_DEFAULT',
value: 'all',
label: <FormattedMessage id="label.all-events" defaultMessage="All events" />,
};
export default function EventsTable({ websiteId, ...props }) {
const [eventType, setEventType] = useState(EVENT_FILTER_DEFAULT.value);
const [eventTypes, setEventTypes] = useState([]);
const { resolve, router } = usePageQuery();
const dropDownOptions = [EVENT_FILTER_DEFAULT, ...eventTypes.map(t => ({ value: t, label: t }))];
@ -22,11 +24,16 @@ export default function EventsTable({ websiteId, ...props }) {
props.onDataLoad?.(data);
}
function handleChange(value) {
router.replace(resolve({ eventType: value === 'all' ? undefined : value }));
setEventType(value);
}
return (
<>
{eventTypes?.length > 1 && (
<div className={styles.filter}>
<DropDown value={eventType} options={dropDownOptions} onChange={setEventType} />
<DropDown value={eventType} options={dropDownOptions} onChange={handleChange} />
</div>
)}
<MetricsTable

View File

@ -32,11 +32,11 @@ export default function WebsiteHeader({ websiteId, title, domain, showLink = fal
return (
<PageHeader className="row">
<div className={classNames(styles.title, 'col-12 col-lg-4')}>{header}</div>
<div className={classNames(styles.active, 'col-6 col-lg-4')}>
<div className={classNames(styles.title, 'col-10 col-lg-4 order-1 order-lg-1')}>{header}</div>
<div className={classNames(styles.active, 'col-12 col-lg-4 order-3 order-lg-2')}>
<ActiveUsers websiteId={websiteId} />
</div>
<div className="col-6 col-lg-4">
<div className="col-2 col-lg-4 order-2 order-lg-3">
<ButtonLayout align="right">
<RefreshButton websiteId={websiteId} />
{showLink && (

View File

@ -25,4 +25,8 @@
.active {
justify-content: flex-start;
}
a.link {
display: none;
}
}

View File

@ -0,0 +1,46 @@
import { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { useRouter } from 'next/router';
import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader';
import WebsiteList from 'components/pages/WebsiteList';
import Button from 'components/common/Button';
import DashboardSettingsButton from 'components/settings/DashboardSettingsButton';
import useFetch from 'hooks/useFetch';
import useStore from 'store/app';
import styles from './WebsiteList.module.css';
const selector = state => state.dashboard;
export default function Dashboard() {
const router = useRouter();
const { id } = router.query;
const userId = id?.[0];
const store = useStore(selector);
const { showCharts, limit } = store;
const [max, setMax] = useState(limit);
const { data } = useFetch('/websites', { params: { user_id: userId } });
function handleMore() {
setMax(max + limit);
}
if (!data) {
return null;
}
return (
<Page>
<PageHeader>
<div>Dashboard</div>
<DashboardSettingsButton />
</PageHeader>
<WebsiteList websites={data} showCharts={showCharts} limit={max} />
{max < data.length && (
<Button className={styles.button} onClick={handleMore}>
<FormattedMessage id="label.more" defaultMessage="More" />
</Button>
)}
</Page>
);
}

View File

@ -44,7 +44,13 @@ export default function TestConsole() {
<Page>
<Head>
{typeof window !== 'undefined' && website && (
<script async defer data-website-id={website.website_uuid} src={`${basePath}/umami.js`} />
<script
async
defer
data-website-id={website.website_uuid}
src={`${basePath}/umami.js`}
data-cache="true"
/>
)}
</Head>
<PageHeader>
@ -68,7 +74,7 @@ export default function TestConsole() {
{show && (
<div className={classNames(styles.test, 'row')}>
<div className="col-4">
<PageHeader>Page links</PageHeader>Nmo
<PageHeader>Page links</PageHeader>
<div>
<Link href={`?page=1`}>
<a>page one</a>

View File

@ -1,32 +1,13 @@
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import Link from 'components/common/Link';
import WebsiteChart from 'components/metrics/WebsiteChart';
import Page from 'components/layout/Page';
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
import useFetch from 'hooks/useFetch';
import DashboardSettingsButton from 'components/settings/DashboardSettingsButton';
import Button from 'components/common/Button';
import useStore from 'store/app';
import Arrow from 'assets/arrow-right.svg';
import styles from './WebsiteList.module.css';
const selector = state => state.dashboard;
export default function WebsiteList({ userId }) {
const { data } = useFetch('/websites', { params: { user_id: userId } });
const { showCharts, limit } = useStore(selector);
const [max, setMax] = useState(limit);
function handleMore() {
setMax(max + limit);
}
if (!data) {
return null;
}
if (data.length === 0) {
export default function WebsiteList({ websites, showCharts, limit }) {
if (websites.length === 0) {
return (
<Page>
<EmptyPlaceholder
@ -46,12 +27,9 @@ export default function WebsiteList({ userId }) {
}
return (
<Page>
<div className={styles.menubar}>
<DashboardSettingsButton />
</div>
{data.map(({ website_id, name, domain }, index) =>
index < max ? (
<div>
{websites.map(({ website_id, name, domain }, index) =>
index < limit ? (
<div key={website_id} className={styles.website}>
<WebsiteChart
websiteId={website_id}
@ -63,11 +41,6 @@ export default function WebsiteList({ userId }) {
</div>
) : null,
)}
{max < data.length && (
<Button className={styles.button} onClick={handleMore}>
<FormattedMessage id="label.more" defaultMessage="More" />
</Button>
)}
</Page>
</div>
);
}

View File

@ -7,17 +7,5 @@
.website:last-child {
border-bottom: 0;
margin-bottom: 0;
}
.menubar {
display: flex;
align-items: center;
justify-content: flex-end;
padding-top: 10px;
}
.button {
align-self: center;
margin-bottom: 40px;
margin-bottom: 20px;
}

View File

@ -2,7 +2,7 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
import MenuButton from 'components/common/MenuButton';
import Gear from 'assets/gear.svg';
import useStore, { setDashboard } from 'store/app';
import useStore, { setDashboard, defaultDashboardConfig } from 'store/app';
const selector = state => state.dashboard;
@ -18,7 +18,7 @@ export default function DashboardSettingsButton() {
function handleSelect(value) {
if (value === 'charts') {
setDashboard({ showCharts: !settings.showCharts });
setDashboard({ ...defaultDashboardConfig, showCharts: !settings.showCharts });
}
//setDashboard(value);
}

View File

@ -19,6 +19,7 @@ export default function LanguageButton() {
options={menuOptions}
value={locale}
menuClassName={styles.menu}
buttonVariant="light"
onSelect={handleSelect}
hideLabel
/>

View File

@ -4,7 +4,6 @@ import { useRouter } from 'next/router';
import MenuButton from 'components/common/MenuButton';
import Icon from 'components/common/Icon';
import User from 'assets/user.svg';
import Chevron from 'assets/chevron-down.svg';
import styles from './UserButton.module.css';
import { removeItem } from 'lib/web';
import { AUTH_TOKEN } from 'lib/constants';
@ -42,9 +41,10 @@ export default function UserButton() {
return (
<MenuButton
icon={<Icon icon={<User />} size="large" />}
value={<Icon icon={<Chevron />} size="small" />}
buttonVariant="light"
options={menuOptions}
onSelect={handleSelect}
hideLabel
/>
);
}

View File

@ -9,6 +9,7 @@
"metrics.device.tablet",
"metrics.referrers"
],
"en-GB": "*",
"fr-FR": ["metrics.actions", "metrics.pages"],
"lt-LT": [
"metrics.device.desktop",
@ -31,6 +32,6 @@
"message.powered-by",
"metrics.device.desktop",
"metrics.device.tablet",
"metrics.filter.raw",
],
"metrics.filter.raw"
]
}

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "لتتبع الاحصاىيات لـ {target}, ضع الكود التالي في منطقة {head} في موقعك.",
"message.type-delete": "اكتب {delete} في الحقل التالي لتأكيد الحذف.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "اكتب {reset} في الحقل التالي لتأكيد الحذف.",
"metrics.actions": "اجراءات",
"metrics.average-visit-time": "متوسط وقت الزيارة",
"metrics.bounce-rate": "معدل الارتداد",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "Pro sledování návštěv na {target}, přidejte následující kód do {head} části vašeho webu.",
"message.type-delete": "Napište {delete} pro potvrzení.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Napište {reset} pro potvrzení.",
"metrics.actions": "Akce",
"metrics.average-visit-time": "Průměrný čas návštěvy",
"metrics.bounce-rate": "Okamžité opuštění",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "For at spore statistik for {target} skal du placere følgende kode i {head} sektionen på dit websted.",
"message.type-delete": "Skriv {delete} i boksen nedenfor, for at bekræfte.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Skriv {reset} i boksen nedenfor, for at bekræfte.",
"metrics.actions": "Handlinger",
"metrics.average-visit-time": "Gennemsnitlig besøgstid",
"metrics.bounce-rate": "Afvisningsprocent",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "Για να παρακολουθείτε στατιστικά στοιχεία για {target}, τοποθετήστε τον ακόλουθο κώδικα στην ενότητα {head} του ιστότοπού σας.",
"message.type-delete": "Πληκτρολογήστε {delete} στο παρακάτω πλαίσιο για επιβεβαίωση.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Πληκτρολογήστε {reset} στο παρακάτω πλαίσιο για επιβεβαίωση.",
"metrics.actions": "Ενέργειες",
"metrics.average-visit-time": "Μέσος χρόνος επίσκεψης",
"metrics.bounce-rate": "Ποσοστό αναπήδησης",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "برای ردیابی آمار {target}, کد روبرو را در قسمت {head} وب‌سایت قرار دهید.",
"message.type-delete": "جهت اطمینان '{delete}' را در کادر زیر بنویسید.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "جهت اطمینان '{reset}' را در کادر زیر بنویسید.",
"metrics.actions": "اقدامات",
"metrics.average-visit-time": "میانگین زمان بازدید",
"metrics.bounce-rate": "نرخ Bounce",

View File

@ -4,8 +4,8 @@
"label.add-website": "Lisää verkkosivu",
"label.administrator": "Järjestelmänvalvoja",
"label.all": "Kaikki",
"label.all-events": "All events",
"label.all-time": "All time",
"label.all-events": "Kaikki tapahtumat",
"label.all-time": "Alusta lähtien",
"label.all-websites": "Kaikki verkkosivut",
"label.back": "Takaisin",
"label.cancel": "Peruuta",
@ -13,7 +13,7 @@
"label.confirm-password": "Vahvista salasana",
"label.copy-to-clipboard": "Kopioi leikepöydälle",
"label.current-password": "Nykyinen salasana",
"label.custom-range": "Mukautettu jakso",
"label.custom-range": "Mukautettu ajanjakso",
"label.dashboard": "Ohjauspaneeli",
"label.date-range": "Ajanjakso",
"label.default-date-range": "Oletusajanjakso",
@ -28,30 +28,30 @@
"label.enable-share-url": "Ota jakamisen URL-osoite käyttöön",
"label.invalid": "Virheellinen",
"label.invalid-domain": "Virheellinen verkkotunnus",
"label.language": "Language",
"label.last-days": "Viimeisimmät {x} päivät",
"label.last-hours": "Viimeisimmät {x} tunnit",
"label.language": "Kieli",
"label.last-days": "Viimeisimmät {x} päivää",
"label.last-hours": "Viimeisimmät {x} tuntia",
"label.logged-in-as": "Kirjautuneena sisään nimellä {username}",
"label.login": "Kirjaudu sisään",
"label.logout": "Kirjaudu ulos",
"label.more": "Lisää",
"label.name": "Nimi",
"label.new-password": "Uusi salasana",
"label.owner": "Owner",
"label.owner": "Omistaja",
"label.password": "Salasana",
"label.passwords-dont-match": "Salasanat eivät täsmää",
"label.profile": "Profiili",
"label.realtime": "Reaaliaikainen",
"label.realtime": "Juuri nyt",
"label.realtime-logs": "Reaaliaikaiset lokit",
"label.refresh": "Päivitä",
"label.required": "Vaaditaan",
"label.reset": "Nollaa",
"label.reset-website": "Reset statistics",
"label.reset-website": "Nollaa tilastot",
"label.save": "Tallenna",
"label.settings": "Asetukset",
"label.share-url": "Jaa URL",
"label.single-day": "Yksi päivä",
"label.theme": "Theme",
"label.theme": "Teema",
"label.this-month": "Tämä kuukausi",
"label.this-week": "Tämä viikko",
"label.this-year": "Tämä vuosi",
@ -62,29 +62,29 @@
"label.username": "Käyttäjänimi",
"label.view-details": "Katso tiedot",
"label.websites": "Verkkosivut",
"message.active-users": "{x} nykyinen {x, plural, one {yksi} other {muut}}",
"message.confirm-delete": "Haluatko varmasti poistaa {target}?",
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.active-users": "{x} {x, plural, one {vierailija} other {vierailijaa}}",
"message.confirm-delete": "Haluatko varmasti poistaa sivuston {target}?",
"message.confirm-reset": "Haluatko varmasti poistaa sivuston {target} tilastot?",
"message.copied": "Kopioitu!",
"message.delete-warning": "Kaikki siihen liittyvät tiedot poistetaan.",
"message.failure": "Jotain meni väärin.",
"message.failure": "Jotain meni pieleen.",
"message.get-share-url": "Hanki jakamisen URL-osoite",
"message.get-tracking-code": "Hanki seurantakoodi",
"message.go-to-settings": "Mene asetuksiin",
"message.incorrect-username-password": "Väärä käyttäjänimi/salasana.",
"message.log.visitor": "Vierailija maasta {country} käyttäen selainta {browser} {os}-laitteella: {device}",
"message.log.visitor": "Vierailija maasta {country} selaimella {browser} laitteella {os} {device}",
"message.new-version-available": "Uusi versio umamista {version} on käytettävissä!",
"message.no-data-available": "Tietoja ei ole käytettävissä.",
"message.no-websites-configured": "Sinulla ei ole määritettyjä verkkosivustoja.",
"message.page-not-found": "Sivua ei löydetty.",
"message.powered-by": "Voimanlähteenä {name}",
"message.reset-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.",
"message.reset-warning": "Kaikki sivuston tilastot poistetaan, mutta seurantakoodi pysyy muuttumattomana.",
"message.save-success": "Tallennettu onnistuneesti.",
"message.share-url": "Tämä on julkisesti jaettu URL-osoitteelle {target}.",
"message.toggle-charts": "Toggle charts",
"message.track-stats": "Jos haluat seurata kohteen {target} tilastoja, aseta seuraava koodi verkkosivustosi {head} osioon.",
"message.type-delete": "Kirjoita {delete} alla olevaan ruutuun vahvistaaksesi.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.share-url": "Tämä on julkisesti jaettu URL sivustolle {target}.",
"message.toggle-charts": "Kytke kaaviot päälle/pois",
"message.track-stats": "Jos haluat seurata sivuston {target} tilastoja, aseta seuraava koodi verkkosivustosi {head}-osioon.",
"message.type-delete": "Kirjoita {delete} alla olevaan kenttään vahvistaaksesi.",
"message.type-reset": "Kirjoita {reset} alla olevaan kenttään vahvistaaksesi.",
"metrics.actions": "Toiminnat",
"metrics.average-visit-time": "Keskimääräinen vierailuaika",
"metrics.bounce-rate": "Välitön poistuminen",
@ -92,19 +92,19 @@
"metrics.countries": "Maat",
"metrics.device.desktop": "Pöytäkone",
"metrics.device.laptop": "Kannettava tietokone",
"metrics.device.mobile": "Mobiili",
"metrics.device.mobile": "Puhelin",
"metrics.device.tablet": "Tabletti",
"metrics.devices": "Laitteet",
"metrics.events": "Tapahtumat",
"metrics.filter.combined": "Yhdistetty",
"metrics.filter.domain-only": "Vain verkkotunnus",
"metrics.filter.raw": "Käsittelemätön",
"metrics.languages": "Languages",
"metrics.languages": "Kielet",
"metrics.operating-systems": "Käyttöjärjestelmät",
"metrics.page-views": "Sivun näyttökertoja",
"metrics.page-views": "Sivun näyttökerrat",
"metrics.pages": "Sivut",
"metrics.referrers": "Viittaajat",
"metrics.unique-visitors": "Uniikit vierailijat",
"metrics.views": "Näyttökertoja",
"metrics.unique-visitors": "Yksittäiset kävijät",
"metrics.views": "Näyttökerrat",
"metrics.visitors": "Vierailijat"
}

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "Fyri at spora hagtøl fyri {target}, koyr kotuna í {head} partin á tínari heimasíðu.",
"message.type-delete": "Skriva {delete} í feltið fyri at vátta",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Skriva {reset} í feltið fyri at vátta",
"metrics.actions": "Gerðir",
"metrics.average-visit-time": "Miðal vitjurnartíð ",
"metrics.bounce-rate": "Bounce prosenttal",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "יש להוסיף את הקוד הבא לאזור ה-{head} של האתר",
"message.type-delete": "הקלידו {delete} בתיבה על מנת לאשר",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "הקלידו {reset} בתיבה על מנת לאשר",
"metrics.actions": "פעולות",
"metrics.average-visit-time": "זמן ביקור ממוצע",
"metrics.bounce-rate": "Bounce rate",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "{target} के आँकड़ों को ट्रैक करने के लिए, अपनी वेबसाइट के {head} अनुभाग में निम्नलिखित कोड रखें।",
"message.type-delete": "पुष्टि करने के लिए नीचे दिए गए बॉक्स में {delete} टाइप करें।",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "ुष्टि करने के लिए नीचे दिए गए बॉक्स में {reset} टाइप करें।",
"metrics.actions": "कार्य",
"metrics.average-visit-time": "औसत दृश्य समय",
"metrics.bounce-rate": "उछाल दर",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "{target} statisztikáinak nyomon követéséhez, helyezd el az alábbi kódot a weboldalad {head} részébe.",
"message.type-delete": "Megerősítéshez írd be az alábbi mezőbe azt, hogy {delete}.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Megerősítéshez írd be az alábbi mezőbe azt, hogy {reset}.",
"metrics.actions": "Műveletek",
"metrics.average-visit-time": "Átlagos látogatási idő",
"metrics.bounce-rate": "Visszafordulási arány",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "Untuk melacak statistik {target}, tempatkan kode berikut di bagian {head} situs web anda.",
"message.type-delete": "Ketikkan {delete} pada kotak di bawah untuk konfirmasi.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Ketikkan {reset} pada kotak di bawah untuk konfirmasi.",
"metrics.actions": "Aksi",
"metrics.average-visit-time": "Waktu kunjungan rata-rata",
"metrics.bounce-rate": "Rasio pentalan",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "{target}のアクセス解析を開始するには、次のコードをWebサイトの{head}セクションへ追加してください。",
"message.type-delete": "確認のため、下のフォームに{delete}と入力してください。",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "確認のため、下のフォームに{reset}と入力してください。",
"metrics.actions": "アクション",
"metrics.average-visit-time": "平均滞在時間",
"metrics.bounce-rate": "直帰率",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "{target}에 대한 통계를 추적하려면 웹사이트의 {head} 섹션에 다음 코드를 입력하십시오.",
"message.type-delete": "확인을 위해 아래 박스에 {delete}값을 입력하십시오.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "확인을 위해 아래 박스에 {reset}값을 입력하십시오.",
"metrics.actions": "액션",
"metrics.average-visit-time": "평균 방문 시간",
"metrics.bounce-rate": "이탈률",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "{target} вебийн статистикийг бүртгэхийн тулд доорх кодыг вебийнхээ {head} хэсэгт байрлуулна уу.",
"message.type-delete": "Доорх хэсэгт {delete} гэж бичиж баталгаажуулна уу.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Доорх хэсэгт {reset} гэж бичиж баталгаажуулна уу.",
"metrics.actions": "Үйлдлүүд",
"metrics.average-visit-time": "Зочилсон дундаж хугацаа",
"metrics.bounce-rate": "Нэг хуудас үзээд гарсан",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "Untuk menjejak statistik bagi {target}, letakkan kod berikut di bahagian {head} laman web anda.",
"message.type-delete": "Taip {delete} di dalam kotak di bawah untuk pengesahan.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Taip {reset} di dalam kotak di bawah untuk pengesahan.",
"metrics.actions": "Aksi",
"metrics.average-visit-time": "Purata tempoh masa lawatan",
"metrics.bounce-rate": "Kadar lantunan",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "For å spore statistikk for {target}, plasser følgende kode i {head}-delen av nettstedet ditt.",
"message.type-delete": "Skriv inn {delete} i boksen nedenfor for å bekrefte.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Skriv inn {reset} i boksen nedenfor for å bekrefte.",
"metrics.actions": "Handlinger",
"metrics.average-visit-time": "Gjennomsnittlig besøkelsestid",
"metrics.bounce-rate": "Avvisningsfrekvens",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "Om statistieken voor {target} bij te houden, plaats je de volgende code in het {head} gedeelte van je website.",
"message.type-delete": "Type {delete} in onderstaande veld om dit te bevestigen.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Type {reset} in onderstaande veld om dit te bevestigen.",
"metrics.actions": "Acties",
"metrics.average-visit-time": "Gemiddelde bezoektijd",
"metrics.bounce-rate": "Bouncepercentage",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "Aby śledzić statystyki dla {target}, umieść poniższy kod w sekcji {head} swojej witryny.",
"message.type-delete": "Wpisz {delete} w polu poniżej, aby potwierdzić.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Wpisz {reset} w polu poniżej, aby potwierdzić.",
"metrics.actions": "Działania",
"metrics.average-visit-time": "Średni czas wizyty",
"metrics.bounce-rate": "Współczynnik odrzuceń",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "Para gerar estatística para {target}, coloque o seguinte código no {head} do html do seu site.",
"message.type-delete": "Escreva {delete} abaixo para continuar.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Escreva {reset} abaixo para continuar.",
"metrics.actions": "Ações",
"metrics.average-visit-time": "Tempo médio da visita",
"metrics.bounce-rate": "Taxa de rejeição",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "Pre sledovanie návštev na {target}, pridajte následujúci kód do {head} časti vašeho webu.",
"message.type-delete": "Napíšte {delete} pre potvrdenie.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Napíšte {reset} pre potvrdenie.",
"metrics.actions": "Akcie",
"metrics.average-visit-time": "Priemerný čas návštevy",
"metrics.bounce-rate": "Okamžité opustenie",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "Če želite spremljati statistične podatke za {target}, v {head} del vašega spletnega mesta namestite naslednjo kodo.",
"message.type-delete": "V spodnje polje vnesite {delete} za potrditev.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "V spodnje polje vnesite {reset} za potrditev.",
"metrics.actions": "Dejanja",
"metrics.average-visit-time": "Povprečni čas obiska",
"metrics.bounce-rate": "Zapustna stopnja",

View File

@ -4,8 +4,8 @@
"label.add-website": "Lägg till webbsajt",
"label.administrator": "Administratör",
"label.all": "Alla",
"label.all-events": "All events",
"label.all-time": "All time",
"label.all-events": "Alla händelser",
"label.all-time": "Sedan början",
"label.all-websites": "Alla sajter",
"label.back": "Tillbaka",
"label.cancel": "Avbryt",
@ -28,7 +28,7 @@
"label.enable-share-url": "Aktivera delnings-URL",
"label.invalid": "Ogiltig",
"label.invalid-domain": "Ogiltig domän",
"label.language": "Language",
"label.language": "Språk",
"label.last-days": "Senaste {x} dagarna",
"label.last-hours": "Senaste {x} timmarna",
"label.logged-in-as": "Inloggad som {username}",
@ -37,7 +37,7 @@
"label.more": "Mer",
"label.name": "Namn",
"label.new-password": "Nytt lösenord",
"label.owner": "Owner",
"label.owner": "Ägare",
"label.password": "Lösenord",
"label.passwords-dont-match": "Lösenorden är inte samma",
"label.profile": "Profil",
@ -46,12 +46,12 @@
"label.refresh": "Uppdatera",
"label.required": "Krävs",
"label.reset": "Återställ",
"label.reset-website": "Reset statistics",
"label.reset-website": "Återställ statistik",
"label.save": "Spara",
"label.settings": "Inställningar",
"label.share-url": "Delnings-URL",
"label.single-day": "En dag",
"label.theme": "Theme",
"label.theme": "Tema",
"label.this-month": "Denna månad",
"label.this-week": "Denna vecka",
"label.this-year": "Detta år",
@ -64,27 +64,27 @@
"label.websites": "Webbsajt",
"message.active-users": "{x} {x, plural, one {besökare} other {besökare}} just nu",
"message.confirm-delete": "Är du säker på att du vill radera {target}?",
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.confirm-reset": "Är du säker på att du vill återställa statistiken för {target}?",
"message.copied": "Kopierad!",
"message.delete-warning": "All tillhörande data kommer också raderas.",
"message.failure": "Något gick fel.",
"message.get-share-url": "Visa delnings-URL",
"message.get-tracking-code": "Visa spårningskod",
"message.go-to-settings": "Gå till inställningar",
"message.incorrect-username-password": "Felaktikt användarnamn/lösenord.",
"message.incorrect-username-password": "Felaktigt användarnamn/lösenord.",
"message.log.visitor": "Besökare från {country} med {browser} på {os} {device}",
"message.new-version-available": "En ny version av umami {version} är tillgänglig!",
"message.no-data-available": "Ingen data tillgänglig.",
"message.no-websites-configured": "Du har inga webbsajter.",
"message.page-not-found": "Sidan kan inte hittas.",
"message.powered-by": "Drivs av {name}",
"message.reset-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.",
"message.reset-warning": "All statistik för webbsajten tas bort men spårningskoden förblir oförändrad.",
"message.save-success": "Sparades!",
"message.share-url": "Det här är den offentliga delnings-URL:en {target}.",
"message.toggle-charts": "Toggle charts",
"message.share-url": "Det här är den offentliga delnings-URL:en för {target}.",
"message.toggle-charts": "Visa/göm grafer",
"message.track-stats": "För att spåra statistik för {target}, placera följande kod i {head}-avsnittet på din webbsajt.",
"message.type-delete": "Skriv {delete} i rutan nedan för att radera.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Skriv {reset} i rutan nedan för att bekräfta.",
"metrics.actions": "Händelser",
"metrics.average-visit-time": "Medelbesökstid",
"metrics.bounce-rate": "Avvisningfrekvens",
@ -99,7 +99,7 @@
"metrics.filter.combined": "Kombinerade",
"metrics.filter.domain-only": "Endast domän",
"metrics.filter.raw": "Rådata",
"metrics.languages": "Languages",
"metrics.languages": "Språk",
"metrics.operating-systems": "Operativsystem",
"metrics.page-views": "Sidvisningar",
"metrics.pages": "Sidor",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "{target}க்கான புள்ளிவிவரங்களைக் கண்காணிக்க, {head}ல் பின்வரும் குறியீட்டை வைக்கவும்.",
"message.type-delete": "உறுதிப்படுத்த கீழே உள்ள பெட்டியில் {delete} என தட்டச்சு செய்க.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "உறுதிப்படுத்த கீழே உள்ள பெட்டியில் {reset} என தட்டச்சு செய்க.",
"metrics.actions": "செயல்கள்",
"metrics.average-visit-time": "சராசரி வருகை நேரம்",
"metrics.bounce-rate": "துள்ளல் விகிதம்",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "{target} alanı adı istatistiklerini takip etmek için, aşağıdaki kodu web sitenizin {head} bloğuna yerleştirin.",
"message.type-delete": "Onaylamak için kutuya {delete} yazın.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Onaylamak için kutuya {reset} yazın.",
"metrics.actions": "Hareketler",
"metrics.average-visit-time": "Ortalama ziyaret süresi",
"metrics.bounce-rate": ıkma oranı",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "Để theo dõi {target}, dán mã theo dõi vào {head} của website bạn.",
"message.type-delete": "Nhập {delete} bên dưới để xác nhận.",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "Nhập {reset} bên dưới để xác nhận.",
"metrics.actions": "Hành động",
"metrics.average-visit-time": "Thời gian truy cập trung bình",
"metrics.bounce-rate": "Tỷ lệ thoát trang",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "把以下代码放到你的网站的 {head} 部分来收集 {target} 的数据。",
"message.type-delete": "在下方输入框输入 {delete} 以确认删除。",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "在下方输入框输入 {reset} 以确认删除。",
"metrics.actions": "用户行为",
"metrics.average-visit-time": "平均访问时间",
"metrics.bounce-rate": "跳出率",

View File

@ -84,7 +84,7 @@
"message.toggle-charts": "Toggle charts",
"message.track-stats": "將以下代碼放入被設定網站的 {head} 部分來收集 {target} 的資料。",
"message.type-delete": "在下方空格輸入 {delete} 確認",
"message.type-reset": "Type {reset} in the box below to confirm.",
"message.type-reset": "在下方空格輸入 {reset} 確認",
"metrics.actions": "用戶行為",
"metrics.average-visit-time": "平均訪問時間",
"metrics.bounce-rate": "跳出率",

View File

@ -1,5 +1,5 @@
import { BROWSERS } from './constants';
import { removeTrailingSlash, removeWWW, getDomainName } from './url';
import { removeTrailingSlash, removeWWW } from './url';
export const urlFilter = (data, { raw }) => {
const isValidUrl = url => {
@ -46,23 +46,18 @@ export const urlFilter = (data, { raw }) => {
};
export const refFilter = (data, { domain, domainOnly, raw }) => {
const domainName = getDomainName(domain);
const regex = new RegExp(`http[s]?://${domainName}`);
const regex = new RegExp(`http[s]?://([a-z0-9-]+\\.)*${domain}`);
const links = {};
const isValidRef = ref => {
return ref !== '' && ref !== null && !ref.startsWith('/') && !ref.startsWith('#');
};
if (raw) {
return data.filter(({ x }) => isValidRef(x) && !regex.test(x));
}
const cleanUrl = url => {
try {
const { hostname, origin, pathname, searchParams, protocol } = new URL(url);
if (hostname === domainName) {
if (regex.test(url)) {
return null;
}
@ -88,6 +83,10 @@ export const refFilter = (data, { domain, domainOnly, raw }) => {
}
};
if (raw) {
return data.filter(({ x }) => isValidRef(x) && !regex.test(x));
}
const map = data.reduce((obj, { x, y }) => {
if (!isValidRef(x)) {
return obj;

View File

@ -86,3 +86,11 @@ export async function getClientInfo(req, { screen }) {
return { userAgent, browser, os, ip, country, device };
}
export function getJsonBody(req) {
if ((req.headers['content-type'] || '').indexOf('text/plain') !== -1) {
return JSON.parse(req.body);
}
return req.body;
}

View File

@ -1,7 +1,15 @@
export function ok(res, data = {}) {
return json(res, data);
}
export function json(res, data = {}) {
return res.status(200).json(data);
}
export function send(res, data) {
return res.status(200).send(data);
}
export function redirect(res, url) {
res.setHeader('Location', url);

View File

@ -1,9 +1,9 @@
import { getWebsiteByUuid, getSessionByUuid, createSession } from 'lib/queries';
import { getClientInfo } from 'lib/request';
import { getJsonBody, getClientInfo } from 'lib/request';
import { uuid, isValidUuid, parseToken } from 'lib/crypto';
export async function getSession(req) {
const { payload } = req.body;
const { payload } = getJsonBody(req);
if (!payload) {
throw new Error('Invalid request');
@ -32,7 +32,7 @@ export async function getSession(req) {
}
const { website_id } = website;
const session_uuid = uuid(website_id, hostname, ip, userAgent, os);
const session_uuid = uuid(website_id, hostname, ip, userAgent);
let session = await getSessionByUuid(session_uuid);

View File

@ -1,6 +1,6 @@
{
"name": "umami",
"version": "1.27.0",
"version": "1.28.0",
"description": "A simple, fast, website analytics alternative to Google Analytics.",
"author": "Mike Cao <mike@mikecao.com>",
"license": "MIT",
@ -21,10 +21,11 @@
"build-geo": "node scripts/build-geo.js",
"build-db-schema": "dotenv prisma introspect",
"build-db-client": "dotenv prisma generate",
"build-mysql-schema": "dotenv prisma introspect -- --schema=./prisma/schema.mysql.prisma",
"build-mysql-schema": "dotenv prisma db pull -- --schema=./prisma/schema.mysql.prisma",
"build-mysql-client": "dotenv prisma generate -- --schema=./prisma/schema.mysql.prisma",
"build-postgresql-schema": "dotenv prisma introspect -- --schema=./prisma/schema.postgresql.prisma",
"build-postgresql-schema": "dotenv prisma db pull -- --schema=./prisma/schema.postgresql.prisma",
"build-postgresql-client": "dotenv prisma generate -- --schema=./prisma/schema.postgresql.prisma",
"postbuild": "node scripts/postbuild.js",
"copy-db-schema": "node scripts/copy-db-schema.js",
"generate-lang": "npm-run-all extract-lang merge-lang",
"extract-lang": "formatjs extract \"{pages,components}/**/*.js\" --out-file build/messages.json",
@ -52,8 +53,9 @@
]
},
"dependencies": {
"@fontsource/inter": "4.5.4",
"@prisma/client": "3.9.2",
"@fontsource/inter": "4.5.5",
"@prisma/client": "3.11.0",
"async-retry": "^1.3.3",
"bcryptjs": "^2.4.3",
"chalk": "^4.1.1",
"chart.js": "^2.9.4",
@ -65,21 +67,25 @@
"detect-browser": "^5.2.0",
"dotenv": "^10.0.0",
"formik": "^2.2.9",
"fs-extra": "^10.0.1",
"immer": "^9.0.12",
"ipaddr.js": "^2.0.1",
"is-ci": "^3.0.1",
"is-docker": "^3.0.0",
"is-localhost-ip": "^1.4.0",
"isbot": "^3.2.2",
"isbot": "^3.4.5",
"jose": "2.0.5",
"maxmind": "^4.3.2",
"maxmind": "^4.3.6",
"moment-timezone": "^0.5.33",
"next": "12.1.0",
"node-fetch": "^3.2.3",
"prompts": "2.4.2",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-intl": "^5.20.6",
"react-intl": "^5.24.7",
"react-simple-maps": "^2.3.0",
"react-spring": "^9.4.3",
"react-spring": "^9.4.4",
"react-tooltip": "^4.2.21",
"react-use-measure": "^2.0.4",
"react-window": "^1.8.6",
@ -101,21 +107,21 @@
"eslint-config-next": "^12.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.2.0",
"extract-react-intl-messages": "^4.1.1",
"husky": "^7.0.0",
"lint-staged": "^11.0.0",
"npm-run-all": "^4.1.5",
"postcss": "^8.2.15",
"postcss": "^8.4.12",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-import": "^14.0.2",
"postcss-preset-env": "^7.4.2",
"postcss-rtlcss": "^3.3.2",
"prettier": "^2.3.2",
"postcss-rtlcss": "^3.5.3",
"prettier": "^2.6.0",
"prettier-eslint": "^13.0.0",
"prisma": "3.9.2",
"rollup": "^2.69.0",
"prisma": "3.11.0",
"rollup": "^2.70.1",
"rollup-plugin-terser": "^7.0.2",
"stylelint": "^14.5.3",
"stylelint-config-css-modules": "^3.0.0",

View File

@ -1,12 +1,13 @@
import { NextResponse } from 'next/server';
function redirectHTTPS(req) {
const host = req.headers.get('host');
if (
process.env.FORCE_SSL &&
!req.headers.get('host').includes('localhost') &&
req.nextUrl.protocol !== 'https'
process.env.NODE_ENV === 'production' &&
req.nextUrl.protocol === 'http:'
) {
return NextResponse.redirect(`https://${req.headers.get('host')}${req.nextUrl.pathname}`, 301);
return NextResponse.redirect(`https://${host}${req.nextUrl.pathname}`, 301);
}
}

View File

@ -2,8 +2,8 @@ import isbot from 'isbot';
import ipaddr from 'ipaddr.js';
import { savePageView, saveEvent } from 'lib/queries';
import { useCors, useSession } from 'lib/middleware';
import { getIpAddress } from 'lib/request';
import { ok, badRequest } from 'lib/response';
import { getJsonBody, getIpAddress } from 'lib/request';
import { ok, send, badRequest } from 'lib/response';
import { createToken } from 'lib/crypto';
import { removeTrailingSlash } from 'lib/url';
@ -39,10 +39,11 @@ export default async (req, res) => {
await useSession(req, res);
const {
body: { type, payload },
session: { website_id, session_id },
} = req;
const { type, payload } = getJsonBody(req);
let { url, referrer, event_type, event_value } = payload;
if (process.env.REMOVE_TRAILING_SLASH) {
@ -59,5 +60,5 @@ export default async (req, res) => {
const token = await createToken({ website_id, session_id });
return ok(res, token);
return send(res, token);
};

View File

@ -1,14 +1,10 @@
import React from 'react';
import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout';
import WebsiteList from 'components/pages/WebsiteList';
import Dashboard from 'components/pages/Dashboard';
import useRequireLogin from 'hooks/useRequireLogin';
export default function DashboardPage() {
const { loading } = useRequireLogin();
const router = useRouter();
const { id } = router.query;
const userId = id?.[0];
if (loading) {
return null;
@ -16,7 +12,7 @@ export default function DashboardPage() {
return (
<Layout>
<WebsiteList userId={userId} />
<Dashboard />
</Layout>
);
}

View File

@ -648,7 +648,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "اكتب "
},
{
"type": 1,
@ -656,7 +656,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " في الحقل التالي لتأكيد الحذف."
}
],
"metrics.actions": [

View File

@ -648,7 +648,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Napište "
},
{
"type": 1,
@ -656,7 +656,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " pro potvrzení."
}
],
"metrics.actions": [

View File

@ -648,7 +648,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Skriv "
},
{
"type": 1,
@ -656,7 +656,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " i boksen nedenfor, for at bekræfte."
}
],
"metrics.actions": [

View File

@ -648,7 +648,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Πληκτρολογήστε "
},
{
"type": 1,
@ -656,7 +656,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " στο παρακάτω πλαίσιο για επιβεβαίωση."
}
],
"metrics.actions": [

View File

@ -640,15 +640,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
},
{
"type": 1,
"value": "reset"
},
{
"type": 0,
"value": " in the box below to confirm."
"value": "جهت اطمینان {reset} را در کادر زیر بنویسید."
}
],
"metrics.actions": [

View File

@ -32,13 +32,13 @@
"label.all-events": [
{
"type": 0,
"value": "All events"
"value": "Kaikki tapahtumat"
}
],
"label.all-time": [
{
"type": 0,
"value": "All time"
"value": "Alusta lähtien"
}
],
"label.all-websites": [
@ -86,7 +86,7 @@
"label.custom-range": [
{
"type": 0,
"value": "Mukautettu jakso"
"value": "Mukautettu ajanjakso"
}
],
"label.dashboard": [
@ -176,7 +176,7 @@
"label.language": [
{
"type": 0,
"value": "Language"
"value": "Kieli"
}
],
"label.last-days": [
@ -190,7 +190,7 @@
},
{
"type": 0,
"value": " päivät"
"value": " päivää"
}
],
"label.last-hours": [
@ -204,7 +204,7 @@
},
{
"type": 0,
"value": " tunnit"
"value": " tuntia"
}
],
"label.logged-in-as": [
@ -250,7 +250,7 @@
"label.owner": [
{
"type": 0,
"value": "Owner"
"value": "Omistaja"
}
],
"label.password": [
@ -274,7 +274,7 @@
"label.realtime": [
{
"type": 0,
"value": "Reaaliaikainen"
"value": "Juuri nyt"
}
],
"label.realtime-logs": [
@ -304,7 +304,7 @@
"label.reset-website": [
{
"type": 0,
"value": "Reset statistics"
"value": "Nollaa tilastot"
}
],
"label.save": [
@ -334,7 +334,7 @@
"label.theme": [
{
"type": 0,
"value": "Theme"
"value": "Teema"
}
],
"label.this-month": [
@ -404,7 +404,7 @@
},
{
"type": 0,
"value": " nykyinen "
"value": " "
},
{
"offset": 0,
@ -413,7 +413,7 @@
"value": [
{
"type": 0,
"value": "yksi"
"value": "vierailija"
}
]
},
@ -421,7 +421,7 @@
"value": [
{
"type": 0,
"value": "muut"
"value": "vierailijaa"
}
]
}
@ -434,7 +434,7 @@
"message.confirm-delete": [
{
"type": 0,
"value": "Haluatko varmasti poistaa "
"value": "Haluatko varmasti poistaa sivuston "
},
{
"type": 1,
@ -448,7 +448,7 @@
"message.confirm-reset": [
{
"type": 0,
"value": "Are your sure you want to reset "
"value": "Haluatko varmasti poistaa sivuston "
},
{
"type": 1,
@ -456,7 +456,7 @@
},
{
"type": 0,
"value": "'s statistics?"
"value": " tilastot?"
}
],
"message.copied": [
@ -474,7 +474,7 @@
"message.failure": [
{
"type": 0,
"value": "Jotain meni väärin."
"value": "Jotain meni pieleen."
}
],
"message.get-share-url": [
@ -512,7 +512,7 @@
},
{
"type": 0,
"value": " käyttäen selainta "
"value": " selaimella "
},
{
"type": 1,
@ -520,7 +520,7 @@
},
{
"type": 0,
"value": " "
"value": " laitteella "
},
{
"type": 1,
@ -528,7 +528,7 @@
},
{
"type": 0,
"value": "-laitteella: "
"value": " "
},
{
"type": 1,
@ -580,7 +580,7 @@
"message.reset-warning": [
{
"type": 0,
"value": "All statistics for this website will be deleted, but your tracking code will remain intact."
"value": "Kaikki sivuston tilastot poistetaan, mutta seurantakoodi pysyy muuttumattomana."
}
],
"message.save-success": [
@ -592,7 +592,7 @@
"message.share-url": [
{
"type": 0,
"value": "Tämä on julkisesti jaettu URL-osoitteelle "
"value": "Tämä on julkisesti jaettu URL sivustolle "
},
{
"type": 1,
@ -606,13 +606,13 @@
"message.toggle-charts": [
{
"type": 0,
"value": "Toggle charts"
"value": "Kytke kaaviot päälle/pois"
}
],
"message.track-stats": [
{
"type": 0,
"value": "Jos haluat seurata kohteen "
"value": "Jos haluat seurata sivuston "
},
{
"type": 1,
@ -628,7 +628,7 @@
},
{
"type": 0,
"value": " osioon."
"value": "-osioon."
}
],
"message.type-delete": [
@ -642,13 +642,13 @@
},
{
"type": 0,
"value": " alla olevaan ruutuun vahvistaaksesi."
"value": " alla olevaan kenttään vahvistaaksesi."
}
],
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Kirjoita "
},
{
"type": 1,
@ -656,7 +656,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " alla olevaan kenttään vahvistaaksesi."
}
],
"metrics.actions": [
@ -704,7 +704,7 @@
"metrics.device.mobile": [
{
"type": 0,
"value": "Mobiili"
"value": "Puhelin"
}
],
"metrics.device.tablet": [
@ -746,7 +746,7 @@
"metrics.languages": [
{
"type": 0,
"value": "Languages"
"value": "Kielet"
}
],
"metrics.operating-systems": [
@ -758,7 +758,7 @@
"metrics.page-views": [
{
"type": 0,
"value": "Sivun näyttökertoja"
"value": "Sivun näyttökerrat"
}
],
"metrics.pages": [
@ -776,13 +776,13 @@
"metrics.unique-visitors": [
{
"type": 0,
"value": "Uniikit vierailijat"
"value": "Yksittäiset kävijät"
}
],
"metrics.views": [
{
"type": 0,
"value": "Näyttökertoja"
"value": "Näyttökerrat"
}
],
"metrics.visitors": [

View File

@ -648,7 +648,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Skriva "
},
{
"type": 1,
@ -656,7 +656,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " í feltið fyri at vátta"
}
],
"metrics.actions": [

View File

@ -628,7 +628,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "הקלידו "
},
{
"type": 1,
@ -636,7 +636,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " בתיבה על מנת לאשר"
}
],
"metrics.actions": [

View File

@ -636,7 +636,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "ुष्टि करने के लिए नीचे दिए गए बॉक्स में "
},
{
"type": 1,
@ -644,7 +644,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " टाइप करें।"
}
],
"metrics.actions": [

View File

@ -648,7 +648,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Megerősítéshez írd be az alábbi mezőbe azt, hogy "
},
{
"type": 1,
@ -656,7 +656,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": "."
}
],
"metrics.actions": [

View File

@ -616,7 +616,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Ketikkan "
},
{
"type": 1,
@ -624,7 +624,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " pada kotak di bawah untuk konfirmasi."
}
],
"metrics.actions": [

View File

@ -620,7 +620,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "確認のため、下のフォームに"
},
{
"type": 1,
@ -628,7 +628,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": "と入力してください。"
}
],
"metrics.actions": [

View File

@ -620,7 +620,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "확인을 위해 아래 박스에 "
},
{
"type": 1,
@ -628,7 +628,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": "값을 입력하십시오."
}
],
"metrics.actions": [

View File

@ -648,7 +648,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Доорх хэсэгт "
},
{
"type": 1,
@ -656,7 +656,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " гэж бичиж баталгаажуулна уу."
}
],
"metrics.actions": [

View File

@ -640,7 +640,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Taip "
},
{
"type": 1,
@ -648,7 +648,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " di dalam kotak di bawah untuk pengesahan."
}
],
"metrics.actions": [

View File

@ -652,7 +652,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Skriv inn "
},
{
"type": 1,
@ -660,7 +660,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " i boksen nedenfor for å bekrefte."
}
],
"metrics.actions": [

View File

@ -656,7 +656,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " in onderstaande veld om dit te bevestigen."
}
],
"metrics.actions": [

View File

@ -648,7 +648,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Wpisz "
},
{
"type": 1,
@ -656,7 +656,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " w polu poniżej, aby potwierdzić."
}
],
"metrics.actions": [

View File

@ -652,7 +652,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Escreva "
},
{
"type": 1,
@ -660,7 +660,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " abaixo para continuar."
}
],
"metrics.actions": [

View File

@ -648,7 +648,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Napíšte "
},
{
"type": 1,
@ -656,7 +656,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " pre potvrdenie."
}
],
"metrics.actions": [

View File

@ -648,7 +648,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "V spodnje polje vnesite "
},
{
"type": 1,
@ -656,7 +656,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " za potrditev."
}
],
"metrics.actions": [

View File

@ -32,13 +32,13 @@
"label.all-events": [
{
"type": 0,
"value": "All events"
"value": "Alla händelser"
}
],
"label.all-time": [
{
"type": 0,
"value": "All time"
"value": "Sedan början"
}
],
"label.all-websites": [
@ -176,7 +176,7 @@
"label.language": [
{
"type": 0,
"value": "Language"
"value": "Språk"
}
],
"label.last-days": [
@ -250,7 +250,7 @@
"label.owner": [
{
"type": 0,
"value": "Owner"
"value": "Ägare"
}
],
"label.password": [
@ -304,7 +304,7 @@
"label.reset-website": [
{
"type": 0,
"value": "Reset statistics"
"value": "Återställ statistik"
}
],
"label.save": [
@ -334,7 +334,7 @@
"label.theme": [
{
"type": 0,
"value": "Theme"
"value": "Tema"
}
],
"label.this-month": [
@ -452,7 +452,7 @@
"message.confirm-reset": [
{
"type": 0,
"value": "Are your sure you want to reset "
"value": "Är du säker på att du vill återställa statistiken för "
},
{
"type": 1,
@ -460,7 +460,7 @@
},
{
"type": 0,
"value": "'s statistics?"
"value": "?"
}
],
"message.copied": [
@ -502,7 +502,7 @@
"message.incorrect-username-password": [
{
"type": 0,
"value": "Felaktikt användarnamn/lösenord."
"value": "Felaktigt användarnamn/lösenord."
}
],
"message.log.visitor": [
@ -584,7 +584,7 @@
"message.reset-warning": [
{
"type": 0,
"value": "All statistics for this website will be deleted, but your tracking code will remain intact."
"value": "All statistik för webbsajten tas bort men spårningskoden förblir oförändrad."
}
],
"message.save-success": [
@ -596,7 +596,7 @@
"message.share-url": [
{
"type": 0,
"value": "Det här är den offentliga delnings-URL:en "
"value": "Det här är den offentliga delnings-URL:en för "
},
{
"type": 1,
@ -610,7 +610,7 @@
"message.toggle-charts": [
{
"type": 0,
"value": "Toggle charts"
"value": "Visa/göm grafer"
}
],
"message.track-stats": [
@ -652,7 +652,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Skriv "
},
{
"type": 1,
@ -660,7 +660,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " i rutan nedan för att bekräfta."
}
],
"metrics.actions": [
@ -750,7 +750,7 @@
"metrics.languages": [
{
"type": 0,
"value": "Languages"
"value": "Språk"
}
],
"metrics.operating-systems": [

View File

@ -640,7 +640,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "உறுதிப்படுத்த கீழே உள்ள பெட்டியில் "
},
{
"type": 1,
@ -648,7 +648,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " என தட்டச்சு செய்க."
}
],
"metrics.actions": [

View File

@ -612,7 +612,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Onaylamak için kutuya "
},
{
"type": 1,
@ -620,7 +620,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " yazın."
}
],
"metrics.actions": [

View File

@ -632,7 +632,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "Nhập "
},
{
"type": 1,
@ -640,7 +640,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " bên dưới để xác nhận."
}
],
"metrics.actions": [

View File

@ -636,7 +636,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "在下方输入框输入 "
},
{
"type": 1,
@ -644,7 +644,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " 以确认删除。"
}
],
"metrics.actions": [

View File

@ -632,7 +632,7 @@
"message.type-reset": [
{
"type": 0,
"value": "Type "
"value": "在下方空格輸入 "
},
{
"type": 1,
@ -640,7 +640,7 @@
},
{
"type": 0,
"value": " in the box below to confirm."
"value": " 確認"
}
],
"metrics.actions": [

View File

@ -23,7 +23,7 @@ files.forEach(file => {
keys.forEach(key => {
const orig = messages[key];
const check = lang[key];
const ignored = ignore[id]?.includes(key);
const ignored = ignore[id] === '*' || ignore[id]?.includes(key);
if (!ignored && (!check || check === orig)) {
console.log(chalk.redBright('*'), chalk.greenBright(`${key}:`), orig);
@ -32,7 +32,7 @@ files.forEach(file => {
});
if (count === 0) {
console.log('**👍 Complete!**');
console.log('**Complete!**');
}
}
});

View File

@ -1,4 +1,4 @@
const fs = require('fs');
const fs = require('fs-extra');
const path = require('path');
const https = require('https');
const chalk = require('chalk');
@ -16,11 +16,9 @@ const asyncForEach = async (array, callback) => {
}
};
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest);
}
const download = async files => {
await fs.ensureDir(dest);
await asyncForEach(files, async file => {
const locale = file.replace('-', '_').replace('.json', '');

View File

@ -1,4 +1,4 @@
const fs = require('fs');
const fs = require('fs-extra');
const path = require('path');
const https = require('https');
const chalk = require('chalk');
@ -16,11 +16,9 @@ const asyncForEach = async (array, callback) => {
}
};
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest);
}
const download = async files => {
await fs.ensureDir(dest);
await asyncForEach(files, async file => {
const locale = file.replace('-', '_').replace('.json', '');

View File

@ -1,4 +1,4 @@
const fs = require('fs');
const fs = require('fs-extra');
const path = require('path');
const del = require('del');
const prettier = require('prettier');
@ -14,22 +14,24 @@ if (removed.length) {
console.log(removed.map(n => `${n} ${chalk.redBright('✗')}`).join('\n'));
}
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest);
async function run() {
await fs.ensureDir(dest);
files.forEach(file => {
const lang = require(`../lang/${file}`);
const keys = Object.keys(lang).sort();
const formatted = keys.reduce((obj, key) => {
obj[key] = { defaultMessage: lang[key] };
return obj;
}, {});
const json = prettier.format(JSON.stringify(formatted), { parser: 'json' });
fs.writeFileSync(path.resolve(dest, file), json);
console.log(path.resolve(src, file), chalk.greenBright('->'), path.resolve(dest, file));
});
}
files.forEach(file => {
const lang = require(`../lang/${file}`);
const keys = Object.keys(lang).sort();
const formatted = keys.reduce((obj, key) => {
obj[key] = { defaultMessage: lang[key] };
return obj;
}, {});
const json = prettier.format(JSON.stringify(formatted), { parser: 'json' });
fs.writeFileSync(path.resolve(dest, file), json);
console.log(path.resolve(src, file), chalk.greenBright('->'), path.resolve(dest, file));
});
run();

10
scripts/postbuild.js Normal file
View File

@ -0,0 +1,10 @@
require('dotenv').config();
const sendTelemetry = require('./telemetry');
async function run() {
if (!process.env.TELEMETRY_DISABLE) {
await sendTelemetry();
}
}
run();

54
scripts/telemetry.js Normal file
View File

@ -0,0 +1,54 @@
const fs = require('fs-extra');
const path = require('path');
const os = require('os');
const retry = require('async-retry');
const isCI = require('is-ci');
const pkg = require('../package.json');
const dest = path.resolve(__dirname, '../.next/cache/umami.json');
const url = 'https://telemetry.umami.is/api/collect';
async function sendTelemetry() {
await fs.ensureFile(dest);
let json = {};
try {
json = await fs.readJSON(dest);
} catch {
// Ignore
}
if (json.version !== pkg.version) {
const { default: isDocker } = await import('is-docker');
const { default: fetch } = await import('node-fetch');
await fs.writeJSON(dest, { version: pkg.version });
const payload = {
umami: pkg.version,
node: process.version,
platform: os.platform(),
arch: os.arch(),
os: `${os.type()} (${os.version()})`,
isDocker: isDocker(),
isCI,
};
await retry(
async () => {
await fetch(url, {
method: 'post',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
},
{ minTimeout: 500, retries: 1, factor: 1 },
).catch(() => {});
}
}
module.exports = sendTelemetry;

View File

@ -9,7 +9,7 @@ import {
} from 'lib/constants';
import { getItem } from 'lib/web';
const defaultDashboardConfig = {
export const defaultDashboardConfig = {
showCharts: true,
limit: DEFAULT_WEBSITE_LIMIT,
};

View File

@ -22,6 +22,7 @@ import { removeTrailingSlash } from '../lib/url';
const autoTrack = attr('data-auto-track') !== 'false';
const dnt = attr('data-do-not-track');
const useCache = attr('data-cache');
const cssEvents = attr('data-css-events') !== 'false';
const domain = attr('data-domains') || '';
const domains = domain.split(',').map(n => n.trim());
@ -29,7 +30,7 @@ import { removeTrailingSlash } from '../lib/url';
const eventSelect = "[class*='umami--']";
const cacheKey = 'umami.cache';
const disableTracking = () =>
const trackingDisabled = () =>
(localStorage && localStorage.getItem('umami.disabled')) ||
(dnt && doNotTrack()) ||
(domain && !domains.includes(hostname));
@ -47,7 +48,7 @@ import { removeTrailingSlash } from '../lib/url';
const post = (url, data, callback) => {
const req = new XMLHttpRequest();
req.open('POST', url, true);
req.setRequestHeader('Content-Type', 'application/json');
req.setRequestHeader('Content-Type', 'text/plain');
req.onreadystatechange = () => {
if (req.readyState === 4) {
@ -58,20 +59,24 @@ import { removeTrailingSlash } from '../lib/url';
req.send(JSON.stringify(data));
};
const collect = (type, params, uuid) => {
if (disableTracking()) return;
const getPayload = () => ({
website,
hostname,
screen,
language,
cache: useCache && sessionStorage.getItem(cacheKey),
url: currentUrl,
});
const payload = {
website: uuid,
hostname,
screen,
language,
cache: useCache && sessionStorage.getItem(cacheKey),
};
Object.keys(params).forEach(key => {
payload[key] = params[key];
const assign = (a, b) => {
Object.keys(b).forEach(key => {
a[key] = b[key];
});
return a;
};
const collect = (type, payload) => {
if (trackingDisabled()) return;
post(
`${root}/api/collect`,
@ -86,28 +91,42 @@ import { removeTrailingSlash } from '../lib/url';
const trackView = (url = currentUrl, referrer = currentRef, uuid = website) => {
collect(
'pageview',
{
assign(getPayload(), {
website: uuid,
url,
referrer,
},
uuid,
}),
);
};
const trackEvent = (event_value, event_type = 'custom', url = currentUrl, uuid = website) => {
collect(
'event',
{
assign(getPayload(), {
website: uuid,
url,
event_type,
event_value,
url,
},
uuid,
}),
);
};
/* Handle events */
const sendEvent = (value, type) => {
const payload = getPayload();
payload.event_type = type;
payload.event_value = value;
const data = JSON.stringify({
type: 'event',
payload,
});
navigator.sendBeacon(`${root}/api/collect`, data);
};
const addEvents = node => {
const elements = node.querySelectorAll(eventSelect);
Array.prototype.forEach.call(elements, addEvent);
@ -120,20 +139,18 @@ import { removeTrailingSlash } from '../lib/url';
const [, type, value] = className.split('--');
const listener = listeners[className]
? listeners[className]
: (listeners[className] = () => trackEvent(value, type));
: (listeners[className] = () => {
if (element.tagName === 'A') {
sendEvent(value, type);
} else {
trackEvent(value, type);
}
});
element.addEventListener(type, listener, true);
});
};
const monitorMutate = mutations => {
mutations.forEach(mutation => {
const element = mutation.target;
addEvent(element);
addEvents(element);
});
};
/* Handle history changes */
const handlePush = (state, title, url) => {
@ -153,6 +170,19 @@ import { removeTrailingSlash } from '../lib/url';
}
};
const observeDocument = () => {
const monitorMutate = mutations => {
mutations.forEach(mutation => {
const element = mutation.target;
addEvent(element);
addEvents(element);
});
};
const observer = new MutationObserver(monitorMutate);
observer.observe(document, { childList: true, subtree: true });
};
/* Global */
if (!window.umami) {
@ -165,20 +195,23 @@ import { removeTrailingSlash } from '../lib/url';
/* Start */
if (autoTrack && !disableTracking()) {
if (autoTrack && !trackingDisabled()) {
history.pushState = hook(history, 'pushState', handlePush);
history.replaceState = hook(history, 'replaceState', handlePush);
const update = () => {
if (document.readyState === 'complete') {
addEvents(document);
trackView();
const observer = new MutationObserver(monitorMutate);
observer.observe(document, { childList: true, subtree: true });
if (cssEvents) {
addEvents(document);
observeDocument();
}
}
};
document.addEventListener('readystatechange', update, true);
update();
}
})(window);

1638
yarn.lock

File diff suppressed because it is too large Load Diff