@nuxtify/pages 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +203 -0
- package/dist/module.cjs +5 -0
- package/dist/module.d.mts +139 -0
- package/dist/module.d.ts +139 -0
- package/dist/module.json +13 -0
- package/dist/module.mjs +138 -0
- package/dist/runtime/components/EmailSubscribeForm.vue +215 -0
- package/dist/runtime/components/FooterCallToAction.vue +38 -0
- package/dist/runtime/components/app/AppAnnouncementBar.vue +40 -0
- package/dist/runtime/components/app/AppBar.vue +128 -0
- package/dist/runtime/components/app/AppDialog.vue +40 -0
- package/dist/runtime/components/app/AppFooter.vue +150 -0
- package/dist/runtime/components/app/AppLoading.vue +16 -0
- package/dist/runtime/components/app/AppLogo.vue +55 -0
- package/dist/runtime/components/app/AppNavigationDrawer.vue +101 -0
- package/dist/runtime/components/app/AppToast.vue +18 -0
- package/dist/runtime/composables/dialog.d.ts +13 -0
- package/dist/runtime/composables/dialog.js +15 -0
- package/dist/runtime/composables/nuxtify.d.ts +2 -0
- package/dist/runtime/composables/nuxtify.js +2 -0
- package/dist/runtime/composables/state.d.ts +15 -0
- package/dist/runtime/composables/state.js +10 -0
- package/dist/runtime/layouts/DefaultLayout.vue +33 -0
- package/dist/runtime/pages/DynamicSlug.vue +23 -0
- package/dist/runtime/pages/IndexPage.vue +23 -0
- package/dist/runtime/server/composables/client.d.ts +1 -0
- package/dist/runtime/server/composables/client.js +1 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/server/utils/client.d.ts +1 -0
- package/dist/runtime/server/utils/client.js +1 -0
- package/dist/runtime/utils/email.d.ts +5 -0
- package/dist/runtime/utils/email.js +26 -0
- package/dist/runtime/utils/formRules.d.ts +18 -0
- package/dist/runtime/utils/formRules.js +22 -0
- package/dist/runtime/utils/icons.d.ts +1 -0
- package/dist/runtime/utils/icons.js +1 -0
- package/dist/runtime/utils/io.d.ts +2 -0
- package/dist/runtime/utils/io.js +19 -0
- package/dist/runtime/utils/math.d.ts +2 -0
- package/dist/runtime/utils/math.js +13 -0
- package/dist/runtime/utils/text.d.ts +23 -0
- package/dist/runtime/utils/text.js +159 -0
- package/dist/runtime/utils/time.d.ts +2 -0
- package/dist/runtime/utils/time.js +7 -0
- package/dist/runtime/utils/url.d.ts +9 -0
- package/dist/runtime/utils/url.js +27 -0
- package/dist/runtime/utils/util.d.ts +7 -0
- package/dist/runtime/utils/util.js +5 -0
- package/dist/runtime/utils/youtube.d.ts +1 -0
- package/dist/runtime/utils/youtube.js +13 -0
- package/dist/types.d.mts +7 -0
- package/dist/types.d.ts +7 -0
- package/package.json +60 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useDisplay, useDrawer, useNuxtifyConfig, mdiArrowTopRight } from '#imports'
|
|
3
|
+
|
|
4
|
+
// App state
|
|
5
|
+
const { smAndDown } = useDisplay()
|
|
6
|
+
const nuxtifyConfig = useNuxtifyConfig()
|
|
7
|
+
const drawer = useDrawer()
|
|
8
|
+
|
|
9
|
+
// Navigation
|
|
10
|
+
const primaryNavLinks = nuxtifyConfig.navigation?.primary
|
|
11
|
+
const secondaryNavLinks = nuxtifyConfig.navigation?.secondary
|
|
12
|
+
const featuredSecondaryLink = secondaryNavLinks?.slice(0, 1)[0] // first link gets featured
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<v-navigation-drawer
|
|
17
|
+
v-if="smAndDown"
|
|
18
|
+
v-model="drawer"
|
|
19
|
+
location="right"
|
|
20
|
+
>
|
|
21
|
+
<nav>
|
|
22
|
+
<v-list nav>
|
|
23
|
+
<!-- Primary links -->
|
|
24
|
+
<v-list-item
|
|
25
|
+
v-for="(link, i) in primaryNavLinks"
|
|
26
|
+
:key="i"
|
|
27
|
+
:to="link.to"
|
|
28
|
+
:href="link.href"
|
|
29
|
+
:prepend-icon="link.icon"
|
|
30
|
+
color="primary"
|
|
31
|
+
:target="link.openInNew ? '_blank' : undefined"
|
|
32
|
+
:rel="link.openInNew ? 'noopener nofollow' : undefined"
|
|
33
|
+
slim
|
|
34
|
+
exact
|
|
35
|
+
>
|
|
36
|
+
<v-list-item-title class="text-subtitle-1 font-weight-bold py-1">
|
|
37
|
+
{{ link.text }}
|
|
38
|
+
<v-icon
|
|
39
|
+
v-if="link.openInNew"
|
|
40
|
+
:icon="mdiArrowTopRight"
|
|
41
|
+
size="x-small"
|
|
42
|
+
color="grey"
|
|
43
|
+
class="ml-1"
|
|
44
|
+
/>
|
|
45
|
+
</v-list-item-title>
|
|
46
|
+
</v-list-item>
|
|
47
|
+
|
|
48
|
+
<!-- Secondary links -->
|
|
49
|
+
<v-list-item
|
|
50
|
+
v-for="link in secondaryNavLinks?.slice(1).reverse()"
|
|
51
|
+
:key="link.text"
|
|
52
|
+
:to="link.to"
|
|
53
|
+
:href="link.href"
|
|
54
|
+
:prepend-icon="link.icon"
|
|
55
|
+
:target="link.openInNew ? '_blank' : undefined"
|
|
56
|
+
:rel="link.openInNew ? 'noopener nofollow' : undefined"
|
|
57
|
+
slim
|
|
58
|
+
exact
|
|
59
|
+
>
|
|
60
|
+
<v-list-item-title class="text-subtitle-1 font-weight-bold py-1">
|
|
61
|
+
{{ link.text }}
|
|
62
|
+
<v-icon
|
|
63
|
+
v-if="link.openInNew"
|
|
64
|
+
:icon="mdiArrowTopRight"
|
|
65
|
+
size="x-small"
|
|
66
|
+
color="grey"
|
|
67
|
+
class="ml-1"
|
|
68
|
+
/>
|
|
69
|
+
</v-list-item-title>
|
|
70
|
+
</v-list-item>
|
|
71
|
+
</v-list>
|
|
72
|
+
</nav>
|
|
73
|
+
|
|
74
|
+
<template #append>
|
|
75
|
+
<!-- Featured secondary link -->
|
|
76
|
+
<div
|
|
77
|
+
v-if="featuredSecondaryLink?.text"
|
|
78
|
+
class="ma-2"
|
|
79
|
+
>
|
|
80
|
+
<v-btn
|
|
81
|
+
:to="featuredSecondaryLink.to"
|
|
82
|
+
:href="featuredSecondaryLink.href"
|
|
83
|
+
:prepend-icon="featuredSecondaryLink.icon"
|
|
84
|
+
variant="flat"
|
|
85
|
+
size="large"
|
|
86
|
+
:target="featuredSecondaryLink.openInNew ? '_blank' : undefined"
|
|
87
|
+
:rel="featuredSecondaryLink.openInNew ? 'noopener nofollow' : undefined"
|
|
88
|
+
block
|
|
89
|
+
>
|
|
90
|
+
{{ featuredSecondaryLink.text }}
|
|
91
|
+
<v-icon
|
|
92
|
+
v-if="featuredSecondaryLink.openInNew"
|
|
93
|
+
:icon="mdiArrowTopRight"
|
|
94
|
+
size="small"
|
|
95
|
+
class="ml-1"
|
|
96
|
+
/>
|
|
97
|
+
</v-btn>
|
|
98
|
+
</div>
|
|
99
|
+
</template>
|
|
100
|
+
</v-navigation-drawer>
|
|
101
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useToast } from '#imports'
|
|
3
|
+
|
|
4
|
+
// App state
|
|
5
|
+
const toast = useToast()
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<v-snackbar
|
|
10
|
+
v-model="toast.show"
|
|
11
|
+
:timeout="5000"
|
|
12
|
+
color="info"
|
|
13
|
+
:min-width="0"
|
|
14
|
+
close-on-content-click
|
|
15
|
+
>
|
|
16
|
+
{{ toast.message }}
|
|
17
|
+
</v-snackbar>
|
|
18
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface Dialog {
|
|
2
|
+
show: boolean;
|
|
3
|
+
title: string;
|
|
4
|
+
message: string;
|
|
5
|
+
action: {
|
|
6
|
+
function: () => Promise<void>;
|
|
7
|
+
buttonText: string;
|
|
8
|
+
buttonColor: string;
|
|
9
|
+
};
|
|
10
|
+
closeButtonText: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const dialogInitialState: Dialog;
|
|
13
|
+
export declare const useDialog: () => import("vue").Ref<Dialog, Dialog>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useState } from "#imports";
|
|
2
|
+
export const dialogInitialState = {
|
|
3
|
+
show: false,
|
|
4
|
+
title: "",
|
|
5
|
+
message: "",
|
|
6
|
+
action: {
|
|
7
|
+
function: () => new Promise((resolve) => resolve()),
|
|
8
|
+
buttonText: "",
|
|
9
|
+
buttonColor: ""
|
|
10
|
+
},
|
|
11
|
+
closeButtonText: "Cancel"
|
|
12
|
+
};
|
|
13
|
+
export const useDialog = () => useState("dialog", () => {
|
|
14
|
+
return dialogInitialState;
|
|
15
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const useDrawer: () => import("vue").Ref<boolean | null, boolean | null>;
|
|
2
|
+
export declare const useToast: () => import("vue").Ref<{
|
|
3
|
+
show: boolean;
|
|
4
|
+
message: string;
|
|
5
|
+
} | {
|
|
6
|
+
show: boolean;
|
|
7
|
+
message: string;
|
|
8
|
+
}, {
|
|
9
|
+
show: boolean;
|
|
10
|
+
message: string;
|
|
11
|
+
} | {
|
|
12
|
+
show: boolean;
|
|
13
|
+
message: string;
|
|
14
|
+
}>;
|
|
15
|
+
export declare const useErrorMessage: () => import("vue").Ref<string, string>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ref, useState } from "#imports";
|
|
2
|
+
export const useDrawer = () => useState("drawer", () => null);
|
|
3
|
+
export const useToast = () => useState(
|
|
4
|
+
"toast",
|
|
5
|
+
() => ref({
|
|
6
|
+
show: false,
|
|
7
|
+
message: ""
|
|
8
|
+
})
|
|
9
|
+
);
|
|
10
|
+
export const useErrorMessage = () => useState("errorMessage", () => "");
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useDisplay, useNuxtifyConfig } from '#imports'
|
|
3
|
+
|
|
4
|
+
// App state
|
|
5
|
+
const nuxtifyConfig = useNuxtifyConfig()
|
|
6
|
+
const { mdAndUp } = useDisplay()
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<v-app>
|
|
11
|
+
<!-- Accessibility -->
|
|
12
|
+
<NuxtRouteAnnouncer />
|
|
13
|
+
|
|
14
|
+
<AppBar class="d-print-none" />
|
|
15
|
+
|
|
16
|
+
<AppNavigationDrawer class="d-print-none" />
|
|
17
|
+
|
|
18
|
+
<AppAnnouncementBar
|
|
19
|
+
v-if="nuxtifyConfig.announcement?.show"
|
|
20
|
+
class="d-print-none"
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
<v-main :min-height="mdAndUp ? 800 : 550">
|
|
24
|
+
<slot />
|
|
25
|
+
</v-main>
|
|
26
|
+
|
|
27
|
+
<AppToast />
|
|
28
|
+
|
|
29
|
+
<v-footer class="bg-primary justify-center mt-8">
|
|
30
|
+
<AppFooter />
|
|
31
|
+
</v-footer>
|
|
32
|
+
</v-app>
|
|
33
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useRoute, capitalizeFirstLetter } from '#imports'
|
|
3
|
+
|
|
4
|
+
// Page info
|
|
5
|
+
const route = useRoute()
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<v-container>
|
|
10
|
+
<v-row justify="center">
|
|
11
|
+
<v-col
|
|
12
|
+
cols="12"
|
|
13
|
+
sm="10"
|
|
14
|
+
lg="8"
|
|
15
|
+
xl="6"
|
|
16
|
+
>
|
|
17
|
+
<h1 class="text-h5 mt-4">
|
|
18
|
+
{{ capitalizeFirstLetter(route.params.slug as string) }}
|
|
19
|
+
</h1>
|
|
20
|
+
</v-col>
|
|
21
|
+
</v-row>
|
|
22
|
+
</v-container>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useServerSeoMeta, useNuxtifyConfig } from '#imports'
|
|
3
|
+
|
|
4
|
+
// Page info
|
|
5
|
+
useServerSeoMeta({
|
|
6
|
+
title: '@nuxtify/pages',
|
|
7
|
+
description: 'This is the @nuxtify/pages homepage.',
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
// App state
|
|
11
|
+
const nuxtifyConfig = useNuxtifyConfig()
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<v-container class="text-center">
|
|
16
|
+
<ClientOnly>
|
|
17
|
+
<h1>{{ nuxtifyConfig.brand?.name }} Home</h1>
|
|
18
|
+
<template #fallback>
|
|
19
|
+
<AppLoading />
|
|
20
|
+
</template>
|
|
21
|
+
</ClientOnly>
|
|
22
|
+
</v-container>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useNuxtifyConfig } from '../../composables/nuxtify.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useNuxtifyConfig } from "../../composables/nuxtify.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { slugify, unslugify } from '../../utils/text.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { slugify, unslugify } from "../../utils/text.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const submitFormData = async (submitUrl, formData) => {
|
|
2
|
+
let isSubmitted = false;
|
|
3
|
+
let isError = false;
|
|
4
|
+
let errorMessage = "";
|
|
5
|
+
try {
|
|
6
|
+
const res = await fetch(submitUrl, {
|
|
7
|
+
method: "POST",
|
|
8
|
+
body: formData
|
|
9
|
+
});
|
|
10
|
+
if (res.ok) {
|
|
11
|
+
isSubmitted = true;
|
|
12
|
+
} else {
|
|
13
|
+
throw new Error(`${res.status} Error`);
|
|
14
|
+
}
|
|
15
|
+
} catch (error) {
|
|
16
|
+
isError = true;
|
|
17
|
+
if (error instanceof Error) {
|
|
18
|
+
errorMessage = error.message;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
isSubmitted,
|
|
23
|
+
isError,
|
|
24
|
+
errorMessage
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const formRules: {
|
|
2
|
+
required: (v: string) => true | "Required";
|
|
3
|
+
requiredArray: <T>(v: Array<T>) => true | "Required";
|
|
4
|
+
validEmail: (v: string) => true | "Must be a valid email address";
|
|
5
|
+
minLength8: (v: string) => true | "Must be at least 8 characters";
|
|
6
|
+
minLength12: (v: string) => true | "Must be at least 12 characters";
|
|
7
|
+
maxLength30: (v: string) => true | "Must not be longer than 30 characters";
|
|
8
|
+
maxLength60: (v: string) => true | "Must not be longer than 60 characters";
|
|
9
|
+
maxLength120: (v: string) => true | "Must not be longer than 120 characters";
|
|
10
|
+
maxLength200: (v: string) => true | "Must not be longer than 200 characters";
|
|
11
|
+
maxLength300: (v: string) => true | "Must not be longer than 300 characters";
|
|
12
|
+
maxLength600: (v: string) => true | "Must not be longer than 600 characters";
|
|
13
|
+
isInteger: (v: string) => true | "Must be an integer";
|
|
14
|
+
gt0: (v: string) => true | "Must be greater than 0";
|
|
15
|
+
gte6: (v: string) => true | "Must be greater than or equal to 6";
|
|
16
|
+
lte12: (v: string) => true | "Must be less than or equal to 12";
|
|
17
|
+
lte365: (v: string) => true | "Must be less than or equal to 365";
|
|
18
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const formRules = {
|
|
2
|
+
// Required
|
|
3
|
+
required: (v) => !!v || "Required",
|
|
4
|
+
requiredArray: (v) => !!v.length || "Required",
|
|
5
|
+
// Text
|
|
6
|
+
validEmail: (v) => /.+@.+/.test(v) || "Must be a valid email address",
|
|
7
|
+
// Length
|
|
8
|
+
minLength8: (v) => v ? v.length >= 8 || "Must be at least 8 characters" : true,
|
|
9
|
+
minLength12: (v) => v ? v.length >= 12 || "Must be at least 12 characters" : true,
|
|
10
|
+
maxLength30: (v) => v ? v.length <= 30 || "Must not be longer than 30 characters" : true,
|
|
11
|
+
maxLength60: (v) => v ? v.length <= 60 || "Must not be longer than 60 characters" : true,
|
|
12
|
+
maxLength120: (v) => v ? v.length <= 120 || "Must not be longer than 120 characters" : true,
|
|
13
|
+
maxLength200: (v) => v ? v.length <= 200 || "Must not be longer than 200 characters" : true,
|
|
14
|
+
maxLength300: (v) => v ? v.length <= 300 || "Must not be longer than 300 characters" : true,
|
|
15
|
+
maxLength600: (v) => v ? v.length <= 600 || "Must not be longer than 600 characters" : true,
|
|
16
|
+
// Number
|
|
17
|
+
isInteger: (v) => Number.isInteger(+v) || "Must be an integer",
|
|
18
|
+
gt0: (v) => Number.parseFloat(v) > 0 || "Must be greater than 0",
|
|
19
|
+
gte6: (v) => Number.parseFloat(v) >= 6 || "Must be greater than or equal to 6",
|
|
20
|
+
lte12: (v) => Number.parseFloat(v) <= 12 || "Must be less than or equal to 12",
|
|
21
|
+
lte365: (v) => Number.parseFloat(v) <= 365 || "Must be less than or equal to 365"
|
|
22
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { mdiArrowRight, mdiClose, mdiMenu, mdiArrowTopRight } from '@mdi/js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { mdiArrowRight, mdiClose, mdiMenu, mdiArrowTopRight } from "@mdi/js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function blobToDataURL(blob) {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
const reader = new FileReader();
|
|
4
|
+
reader.onload = (_e) => resolve(reader.result);
|
|
5
|
+
reader.onerror = (_e) => reject(reader.error);
|
|
6
|
+
reader.onabort = (_e) => reject(new Error("Read aborted"));
|
|
7
|
+
reader.readAsDataURL(blob);
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export function filenameToUrl(filename, timestamped = false) {
|
|
11
|
+
const filenameClean = encodeURIComponent(
|
|
12
|
+
filename.trim().replace(/[^a-z0-9.-]/gi, "-").replace(/-{2,}/g, "-").replace(/\.{2,}/g, ".").replace(/-\./g, ".").replace(/-$/g, "").replace(/^-/g, "").slice(0, 500)
|
|
13
|
+
// limit to the first 500 characters
|
|
14
|
+
);
|
|
15
|
+
if (timestamped) {
|
|
16
|
+
return `${Date.now()}-${filenameClean}`;
|
|
17
|
+
}
|
|
18
|
+
return filenameClean;
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function toPercentage(val, decimals = 0) {
|
|
2
|
+
const percentage = (val * 100).toFixed(decimals);
|
|
3
|
+
return `${percentage}%`;
|
|
4
|
+
}
|
|
5
|
+
export function roundToFixed(val, positions = 2) {
|
|
6
|
+
let text = 0;
|
|
7
|
+
if (typeof val === "string") {
|
|
8
|
+
text = +Number.parseFloat(val).toFixed(positions);
|
|
9
|
+
} else if (typeof val === "number") {
|
|
10
|
+
text = +val.toFixed(positions);
|
|
11
|
+
}
|
|
12
|
+
return text;
|
|
13
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const titleCase: (text: string, skipShortWords?: boolean) => string;
|
|
2
|
+
export declare function capitalizeFirstLetter(text: string): string;
|
|
3
|
+
export declare function fullName(firstName: string, lastName: string): string;
|
|
4
|
+
export declare function parseFullName(fullName: string | undefined | null): {
|
|
5
|
+
firstName: string;
|
|
6
|
+
lastName: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const honoraryName: (name: string, honorificSuffix?: string) => string;
|
|
9
|
+
export declare function getPronouns(gender: string): {
|
|
10
|
+
single: string;
|
|
11
|
+
plural: string;
|
|
12
|
+
possessive: string;
|
|
13
|
+
possessivePlural: string;
|
|
14
|
+
};
|
|
15
|
+
export declare const formatPhone: (input: string, separator?: string) => string;
|
|
16
|
+
export declare function formatDate(date: string, locale?: string): string;
|
|
17
|
+
export declare function formatDateTime(date: string, locale?: string): string;
|
|
18
|
+
export declare function booleanToText(bool: boolean): "Yes" | "No";
|
|
19
|
+
export declare const truncate: (text: string, maxLength?: number, ellipses?: boolean) => string;
|
|
20
|
+
export declare const slugify: (text: string) => string;
|
|
21
|
+
export declare const unslugify: (text: string) => string;
|
|
22
|
+
export declare function pluralize(value: number, units: string, showValue?: boolean): string;
|
|
23
|
+
export declare const getLanguageName: (languageCode: string, displayLanguage?: string) => string | undefined;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
export const titleCase = (text, skipShortWords = true) => {
|
|
2
|
+
const shortWords = ["and", "an", "the", "a", "but", "for", "at", "by", "to"];
|
|
3
|
+
const words = text.split(" ");
|
|
4
|
+
const titleWords = [];
|
|
5
|
+
for (const [i] of words.entries()) {
|
|
6
|
+
if (skipShortWords && shortWords.includes(words[i])) {
|
|
7
|
+
if (i === 0) {
|
|
8
|
+
titleWords.push(words[i].charAt(0).toUpperCase() + words[i].slice(1));
|
|
9
|
+
} else {
|
|
10
|
+
titleWords.push(words[i]);
|
|
11
|
+
}
|
|
12
|
+
} else {
|
|
13
|
+
titleWords.push(words[i].charAt(0).toUpperCase() + words[i].slice(1));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return titleWords.join(" ");
|
|
17
|
+
};
|
|
18
|
+
export function capitalizeFirstLetter(text) {
|
|
19
|
+
if (!text) {
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
23
|
+
}
|
|
24
|
+
export function fullName(firstName, lastName) {
|
|
25
|
+
let name = "";
|
|
26
|
+
if (firstName && lastName) {
|
|
27
|
+
name = `${firstName} ${lastName}`;
|
|
28
|
+
} else if (firstName) {
|
|
29
|
+
name = firstName;
|
|
30
|
+
} else if (lastName) {
|
|
31
|
+
name = lastName;
|
|
32
|
+
}
|
|
33
|
+
return name;
|
|
34
|
+
}
|
|
35
|
+
export function parseFullName(fullName2) {
|
|
36
|
+
let firstName = "";
|
|
37
|
+
let lastName = "";
|
|
38
|
+
if (fullName2) {
|
|
39
|
+
const nameSplit = fullName2.split(" ");
|
|
40
|
+
firstName = nameSplit[0];
|
|
41
|
+
if (nameSplit.length > 1) {
|
|
42
|
+
lastName = nameSplit.slice(1).join(" ");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { firstName, lastName };
|
|
46
|
+
}
|
|
47
|
+
export const honoraryName = (name, honorificSuffix = "") => {
|
|
48
|
+
return honorificSuffix ? `${name}, ${honorificSuffix}` : name;
|
|
49
|
+
};
|
|
50
|
+
export function getPronouns(gender) {
|
|
51
|
+
if (gender === "male") {
|
|
52
|
+
return {
|
|
53
|
+
single: "he",
|
|
54
|
+
plural: "he's",
|
|
55
|
+
possessive: "his",
|
|
56
|
+
possessivePlural: "his"
|
|
57
|
+
};
|
|
58
|
+
} else if (gender === "female") {
|
|
59
|
+
return {
|
|
60
|
+
single: "she",
|
|
61
|
+
plural: "she's",
|
|
62
|
+
possessive: "her",
|
|
63
|
+
possessivePlural: "her's"
|
|
64
|
+
};
|
|
65
|
+
} else {
|
|
66
|
+
return {
|
|
67
|
+
single: "they",
|
|
68
|
+
plural: "they're",
|
|
69
|
+
possessive: "their",
|
|
70
|
+
possessivePlural: "theirs"
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export const formatPhone = (input, separator = "-") => {
|
|
75
|
+
let phone = input;
|
|
76
|
+
if (phone.slice(0, 2) === "+1") {
|
|
77
|
+
phone = phone.slice(2);
|
|
78
|
+
}
|
|
79
|
+
if (phone.length === 10) {
|
|
80
|
+
phone = phone.replace(
|
|
81
|
+
/(\d{3})(\d{3})(\d{4})/,
|
|
82
|
+
`$1${separator}$2${separator}$3`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return phone;
|
|
86
|
+
};
|
|
87
|
+
export function formatDate(date, locale = "en-us") {
|
|
88
|
+
return new Date(date).toLocaleDateString(locale, {
|
|
89
|
+
year: "numeric",
|
|
90
|
+
month: "long",
|
|
91
|
+
day: "numeric"
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
export function formatDateTime(date, locale = "en-us") {
|
|
95
|
+
return new Date(date).toLocaleString(locale, {
|
|
96
|
+
year: "numeric",
|
|
97
|
+
month: "long",
|
|
98
|
+
day: "numeric",
|
|
99
|
+
hour: "numeric",
|
|
100
|
+
minute: "numeric"
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
export function booleanToText(bool) {
|
|
104
|
+
return bool ? "Yes" : "No";
|
|
105
|
+
}
|
|
106
|
+
export const truncate = (text, maxLength = 80, ellipses = true) => {
|
|
107
|
+
if (text.length > maxLength) {
|
|
108
|
+
let trimmedString = text.substring(0, maxLength + 1);
|
|
109
|
+
trimmedString = trimmedString.substring(
|
|
110
|
+
0,
|
|
111
|
+
Math.min(trimmedString.length, trimmedString.lastIndexOf(" "))
|
|
112
|
+
);
|
|
113
|
+
return `${trimmedString}${ellipses ? "..." : ""}`;
|
|
114
|
+
}
|
|
115
|
+
return text;
|
|
116
|
+
};
|
|
117
|
+
export const slugify = (text) => {
|
|
118
|
+
const slug = text.normalize("NFKD").toLowerCase().trim().replace(/\s+/g, "-").replace(/_/g, "-").replace(/-{2,}/g, "-").replace(/-$/g, "");
|
|
119
|
+
return slug;
|
|
120
|
+
};
|
|
121
|
+
export const unslugify = (text) => {
|
|
122
|
+
const unslug = text.replace(/-/g, " ");
|
|
123
|
+
return unslug;
|
|
124
|
+
};
|
|
125
|
+
export function pluralize(value, units, showValue = true) {
|
|
126
|
+
let text = "";
|
|
127
|
+
let unitsPlural = units;
|
|
128
|
+
if (unitsPlural.slice(-1) === "y") {
|
|
129
|
+
unitsPlural = unitsPlural.slice(0, -1) + "ies";
|
|
130
|
+
} else {
|
|
131
|
+
unitsPlural = units + "s";
|
|
132
|
+
}
|
|
133
|
+
if (showValue) {
|
|
134
|
+
if (value === 1) {
|
|
135
|
+
text = `${value} ${units}`;
|
|
136
|
+
} else {
|
|
137
|
+
text = `${value} ${unitsPlural}`;
|
|
138
|
+
}
|
|
139
|
+
} else if (!value || value === 1) {
|
|
140
|
+
text = `${units}`;
|
|
141
|
+
} else {
|
|
142
|
+
text = `${unitsPlural}`;
|
|
143
|
+
}
|
|
144
|
+
return text;
|
|
145
|
+
}
|
|
146
|
+
export const getLanguageName = (languageCode, displayLanguage = "en") => {
|
|
147
|
+
try {
|
|
148
|
+
const languageNames = new Intl.DisplayNames([displayLanguage], {
|
|
149
|
+
type: "language"
|
|
150
|
+
});
|
|
151
|
+
return languageNames.of(languageCode);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error(
|
|
154
|
+
`Error getting language name for code '${languageCode}' with locale '${displayLanguage}':`,
|
|
155
|
+
error
|
|
156
|
+
);
|
|
157
|
+
return languageCode;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2
|
+
export function daysSince(targetDate) {
|
|
3
|
+
const currentDate = /* @__PURE__ */ new Date();
|
|
4
|
+
const timeDifference = currentDate.getTime() - targetDate.getTime();
|
|
5
|
+
const daysDifference = Math.floor(timeDifference / (1e3 * 60 * 60 * 24));
|
|
6
|
+
return daysDifference;
|
|
7
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const getBaseUrl: (url: string) => string;
|
|
2
|
+
export declare const getUtmParams: (url: string) => {
|
|
3
|
+
utmSource: string | null;
|
|
4
|
+
utmMedium: string | null;
|
|
5
|
+
utmCampaign: string | null;
|
|
6
|
+
utmTerm: string | null;
|
|
7
|
+
utmContent: string | null;
|
|
8
|
+
};
|
|
9
|
+
export declare const isExternalUrl: (url: string, hostname: string) => boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const getBaseUrl = (url) => {
|
|
2
|
+
const urlObject = new URL(url);
|
|
3
|
+
urlObject.search = "";
|
|
4
|
+
urlObject.hash = "";
|
|
5
|
+
return urlObject.href;
|
|
6
|
+
};
|
|
7
|
+
export const getUtmParams = (url) => {
|
|
8
|
+
const urlSearchParams = new URLSearchParams(new URL(url).search);
|
|
9
|
+
return {
|
|
10
|
+
utmSource: urlSearchParams.get("utm_source"),
|
|
11
|
+
utmMedium: urlSearchParams.get("utm_medium"),
|
|
12
|
+
utmCampaign: urlSearchParams.get("utm_campaign"),
|
|
13
|
+
utmTerm: urlSearchParams.get("utm_term"),
|
|
14
|
+
utmContent: urlSearchParams.get("utm_content")
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export const isExternalUrl = (url, hostname) => {
|
|
18
|
+
if (!url) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (url.startsWith("/")) {
|
|
22
|
+
return false;
|
|
23
|
+
} else {
|
|
24
|
+
const linkHostname = new URL(url).hostname;
|
|
25
|
+
return linkHostname !== hostname;
|
|
26
|
+
}
|
|
27
|
+
};
|