forked from jsnbuchanan/crowd-funder-for-time-pwa
finish separation of daily reminder message, bump version to 0.3.34
This commit is contained in:
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
|
||||||
|
## [0.3.34] - 2024.11
|
||||||
|
### Added
|
||||||
|
- Daily reliable, hard-coded notification message
|
||||||
|
- Setting to change the partner API server
|
||||||
|
|
||||||
|
|
||||||
## [0.3.33] - 2024.11.07 - adb7b16ecf1343c39cba71a7d6bb0e7a973e1102
|
## [0.3.33] - 2024.11.07 - adb7b16ecf1343c39cba71a7d6bb0e7a973e1102
|
||||||
### Fixed
|
### Fixed
|
||||||
- Affirm Delivery button on offer claim page didn't work.
|
- Affirm Delivery button on offer claim page didn't work.
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "TimeSafari",
|
"name": "TimeSafari",
|
||||||
"version": "0.3.34-beta",
|
"version": "0.3.34",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "TimeSafari",
|
"name": "TimeSafari",
|
||||||
"version": "0.3.34-beta",
|
"version": "0.3.34",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^6.1.2",
|
"@capacitor/android": "^6.1.2",
|
||||||
"@capacitor/cli": "^6.1.2",
|
"@capacitor/cli": "^6.1.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "TimeSafari",
|
"name": "TimeSafari",
|
||||||
"version": "0.3.34-beta",
|
"version": "0.3.34",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
|
|||||||
86
public/img/icons/safari-pinned-tab-512x512.svg
Normal file
86
public/img/icons/safari-pinned-tab-512x512.svg
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M2480 4005 c-25 -7 -58 -20 -75 -29 -16 -9 -40 -16 -52 -16 -17 0
|
||||||
|
-24 -7 -28 -27 -3 -16 -14 -45 -24 -65 -21 -41 -13 -55 18 -38 25 13 67 13 92
|
||||||
|
-1 15 -8 35 -4 87 17 99 39 130 41 197 10 64 -29 77 -31 107 -15 20 11 20 11
|
||||||
|
-3 35 -12 13 -30 24 -38 24 -24 1 -132 38 -148 51 -8 7 -11 20 -7 32 12 37
|
||||||
|
-40 47 -126 22z"/>
|
||||||
|
<path d="M1450 3775 c-7 -8 -18 -15 -24 -15 -7 0 -31 -14 -54 -32 -29 -22 -38
|
||||||
|
-34 -29 -40 17 -11 77 -10 77 1 0 5 16 16 35 25 60 29 220 19 290 -18 17 -9
|
||||||
|
33 -16 37 -16 4 0 31 -15 60 -34 108 -70 224 -215 282 -353 30 -71 53 -190 42
|
||||||
|
-218 -10 -27 -23 -8 -52 75 -30 90 -88 188 -120 202 -13 6 -26 9 -29 6 -3 -2
|
||||||
|
11 -51 30 -108 28 -83 35 -119 35 -179 0 -120 -22 -127 -54 -17 -11 37 -13 21
|
||||||
|
-18 -154 -5 -180 -8 -200 -32 -264 -51 -132 -129 -245 -199 -288 -21 -12 -79
|
||||||
|
-49 -129 -80 -161 -102 -294 -141 -473 -141 -228 0 -384 76 -535 259 -81 99
|
||||||
|
-118 174 -154 312 -31 121 -35 273 -11 437 19 127 19 125 -4 125 -23 0 -51
|
||||||
|
-34 -87 -104 -14 -28 -33 -64 -41 -81 -19 -34 -22 -253 -7 -445 9 -106 12
|
||||||
|
-119 44 -170 19 -30 42 -67 50 -81 64 -113 85 -140 130 -169 28 -18 53 -44 61
|
||||||
|
-62 8 -20 36 -45 83 -76 62 -39 80 -46 151 -54 44 -5 96 -13 115 -18 78 -20
|
||||||
|
238 -31 282 -19 24 6 66 8 95 5 76 -9 169 24 319 114 32 19 80 56 106 82 27
|
||||||
|
26 52 48 58 48 5 0 27 26 50 58 48 66 56 70 132 71 62 1 165 29 238 64 112 55
|
||||||
|
177 121 239 245 37 76 39 113 10 267 -12 61 -23 131 -26 156 -5 46 -5 47 46
|
||||||
|
87 92 73 182 70 263 -8 l51 -49 -6 -61 c-4 -34 -13 -85 -21 -113 -28 -103 -30
|
||||||
|
-161 -4 -228 16 -44 32 -67 55 -83 18 -11 39 -37 47 -58 10 -23 37 -53 73 -81
|
||||||
|
32 -25 69 -57 82 -71 14 -14 34 -26 47 -26 12 0 37 -7 56 -15 20 -8 66 -17
|
||||||
|
104 -20 107 -10 110 -11 150 -71 50 -75 157 -177 197 -187 18 -5 53 -24 78
|
||||||
|
-42 71 -51 176 -82 304 -89 61 -4 127 -12 147 -18 29 -9 45 -8 77 6 23 9 50
|
||||||
|
16 60 16 31 0 163 46 216 76 28 15 75 46 105 69 30 23 69 49 85 58 17 8 46 31
|
||||||
|
64 51 19 20 40 36 47 36 18 0 77 70 100 120 32 66 45 108 55 173 5 32 16 71
|
||||||
|
24 87 43 84 43 376 0 549 -27 105 -43 127 -135 188 -30 21 -65 46 -77 57 -13
|
||||||
|
11 -23 17 -23 14 0 -3 21 -46 47 -94 79 -151 85 -166 115 -263 25 -83 28 -110
|
||||||
|
28 -226 0 -144 -17 -221 -75 -335 -39 -77 -208 -244 -304 -299 -451 -263 -975
|
||||||
|
-67 -1138 426 -23 70 -26 95 -28 254 -1 108 -7 183 -14 196 -6 12 -11 31 -11
|
||||||
|
43 0 32 31 122 52 149 10 13 18 28 18 34 0 5 25 40 56 78 60 73 172 170 219
|
||||||
|
190 30 12 30 13 6 17 -15 2 -29 -2 -37 -12 -6 -9 -16 -16 -22 -16 -6 0 -23
|
||||||
|
-11 -39 -24 -15 -12 -33 -25 -40 -27 -17 -6 -82 -60 -117 -97 -65 -70 -75 -82
|
||||||
|
-107 -133 -23 -34 -35 -46 -37 -35 -3 16 20 87 44 134 6 12 9 34 6 48 -4 22
|
||||||
|
-8 25 -31 19 -14 -3 -38 -15 -53 -26 -34 -24 -34 -21 -6 28 65 112 184 206
|
||||||
|
291 227 15 3 39 9 55 12 l27 6 -24 9 c-90 35 -304 -66 -478 -225 -39 -36 -74
|
||||||
|
-66 -77 -66 -22 0 18 82 72 148 19 23 32 46 28 49 -4 4 -26 13 -49 19 -73 21
|
||||||
|
-161 54 -171 64 -6 6 -20 10 -32 10 -21 0 -21 -1 -8 -40 45 -130 8 -247 -93
|
||||||
|
-299 -25 -13 -31 0 -14 29 15 22 1 33 -22 17 -56 -36 -117 -22 -117 28 0 13
|
||||||
|
-16 47 -35 76 -22 34 -33 60 -29 73 4 16 -3 26 -26 39 -16 10 -30 21 -30 25 1
|
||||||
|
18 54 64 87 76 l38 13 -33 5 c-30 4 -115 -18 -154 -42 -13 -7 -20 -5 -27 8 -9
|
||||||
|
16 -12 16 -53 1 -160 -61 -258 -104 -258 -114 0 -7 10 -20 21 -31 103 -91 217
|
||||||
|
-297 249 -449 28 -135 41 -237 35 -276 -14 -91 -48 -170 -97 -220 -44 -47 -68
|
||||||
|
-60 -68 -40 0 6 4 12 8 15 5 3 24 35 42 72 l33 67 -6 141 c-4 103 -11 158 -26
|
||||||
|
205 -12 35 -21 70 -21 77 0 7 -20 56 -45 108 -82 173 -227 322 -392 401 -67
|
||||||
|
33 -90 39 -163 42 -108 5 -130 10 -130 28 0 20 -63 20 -80 0z"/>
|
||||||
|
<path d="M3710 3765 c0 -20 8 -28 39 -41 22 -8 42 -22 45 -30 5 -14 42 -19 70
|
||||||
|
-8 10 4 -7 21 -58 55 -41 27 -79 49 -85 49 -6 0 -11 -11 -11 -25z"/>
|
||||||
|
<path d="M3173 3734 c-9 -25 10 -36 35 -18 12 8 22 19 22 25 0 16 -50 10 -57
|
||||||
|
-7z"/>
|
||||||
|
<path d="M1982 3728 c6 -16 36 -34 44 -26 3 4 4 14 1 23 -7 17 -51 21 -45 3z"/>
|
||||||
|
<path d="M1540 3620 c0 -5 7 -10 16 -10 8 0 12 5 9 10 -3 6 -10 10 -16 10 -5
|
||||||
|
0 -9 -4 -9 -10z"/>
|
||||||
|
<path d="M4467 3624 c-4 -4 23 -27 60 -50 84 -56 99 -58 67 -9 -28 43 -107 79
|
||||||
|
-127 59z"/>
|
||||||
|
<path d="M655 3552 c-11 -2 -26 -9 -33 -14 -7 -6 -27 -18 -45 -27 -36 -18 -58
|
||||||
|
-64 -39 -83 9 -9 25 1 70 43 53 48 78 78 70 84 -2 1 -12 -1 -23 -3z"/>
|
||||||
|
<path d="M1015 3460 c-112 -24 -247 -98 -303 -165 -53 -65 -118 -214 -136
|
||||||
|
-311 -20 -113 -20 -145 -1 -231 20 -88 49 -153 102 -230 79 -113 186 -182 331
|
||||||
|
-214 108 -24 141 -24 247 1 130 30 202 72 316 181 102 100 153 227 152 384 0
|
||||||
|
142 -58 293 -150 395 -60 67 -180 145 -261 171 -75 23 -232 34 -297 19z m340
|
||||||
|
-214 c91 -43 174 -154 175 -234 0 -18 -9 -51 -21 -73 -19 -37 -19 -42 -5 -64
|
||||||
|
35 -54 12 -121 -48 -142 -22 -7 -47 -19 -55 -27 -9 -8 -41 -27 -71 -42 -50
|
||||||
|
-26 -64 -29 -155 -29 -111 0 -152 14 -206 68 -49 49 -63 85 -64 162 0 59 4 78
|
||||||
|
28 118 31 52 96 105 141 114 23 5 33 17 56 68 46 103 121 130 225 81z"/>
|
||||||
|
<path d="M3985 3464 c-44 -7 -154 -44 -200 -67 -55 -28 -138 -96 -162 -132
|
||||||
|
-10 -16 -39 -75 -64 -130 l-44 -100 0 -160 0 -160 45 -90 c53 -108 152 -214
|
||||||
|
245 -264 59 -31 215 -71 281 -71 53 0 206 40 255 67 98 53 203 161 247 253 53
|
||||||
|
113 74 193 74 280 -1 304 -253 564 -557 575 -49 2 -103 1 -120 -1z m311 -220
|
||||||
|
c129 -68 202 -209 160 -309 -15 -35 -15 -42 -1 -72 26 -55 -3 -118 -59 -129
|
||||||
|
-19 -3 -43 -15 -53 -26 -26 -29 -99 -64 -165 -78 -45 -10 -69 -10 -120 -1 -74
|
||||||
|
15 -113 37 -161 91 -110 120 -50 331 109 385 24 8 44 23 52 39 6 14 18 38 25
|
||||||
|
53 33 72 127 93 213 47z"/>
|
||||||
|
<path d="M487 3394 c-21 -12 -27 -21 -25 -40 2 -14 7 -26 12 -27 14 -3 48 48
|
||||||
|
44 66 -3 14 -6 14 -31 1z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.6 KiB |
73
src/App.vue
73
src/App.vue
@@ -238,6 +238,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="notification.type === 'notification-mute'"
|
v-if="notification.type === 'notification-mute'"
|
||||||
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||||
@@ -277,6 +278,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="notification.type === 'notification-off'"
|
v-if="notification.type === 'notification-off'"
|
||||||
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||||
@@ -286,7 +288,7 @@
|
|||||||
>
|
>
|
||||||
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
||||||
<p class="text-lg mb-4">
|
<p class="text-lg mb-4">
|
||||||
Would you like to <b>turn off</b> notifications for this app?
|
Would you like to <b>turn off</b> this notification?
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -296,7 +298,7 @@
|
|||||||
"
|
"
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2"
|
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2"
|
||||||
>
|
>
|
||||||
Turn Off Notifications
|
Turn Off Notification
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
@click="close(notification.id)"
|
||||||
@@ -318,9 +320,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vue, Component } from "vue-facing-decorator";
|
import { Vue, Component } from "vue-facing-decorator";
|
||||||
|
|
||||||
import { db, logConsoleAndDb } from "@/db/index";
|
import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "@/db/index";
|
||||||
import { NotificationIface } from "./constants/app";
|
import { NotificationIface } from "./constants/app";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class App extends Vue {
|
export default class App extends Vue {
|
||||||
@@ -329,18 +330,29 @@ export default class App extends Vue {
|
|||||||
stopAsking = false;
|
stopAsking = false;
|
||||||
|
|
||||||
async turnOffNotifications(notification: NotificationIface) {
|
async turnOffNotifications(notification: NotificationIface) {
|
||||||
let subscription;
|
let subscription: object | null = null;
|
||||||
const pushProviderSuccess: boolean = await navigator.serviceWorker?.ready
|
|
||||||
|
let allGoingOff = false;
|
||||||
|
const settings = await retrieveSettingsForActiveAccount();
|
||||||
|
const notifyingNewActivity = !!settings?.notifyingNewActivityTime;
|
||||||
|
const notifyingReminder = !!settings?.notifyingReminderTime;
|
||||||
|
if (!notifyingNewActivity || !notifyingReminder) {
|
||||||
|
// the other notification is already off, so fully unsubscribe now
|
||||||
|
allGoingOff = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await navigator.serviceWorker?.ready
|
||||||
.then((registration) => {
|
.then((registration) => {
|
||||||
return registration.pushManager.getSubscription();
|
return registration.pushManager.getSubscription();
|
||||||
})
|
})
|
||||||
.then((subscript: PushSubscription | null) => {
|
.then(async (subscript: PushSubscription | null) => {
|
||||||
subscription = subscript;
|
|
||||||
if (subscript) {
|
if (subscript) {
|
||||||
return subscript.unsubscribe();
|
subscription = subscript.toJSON();
|
||||||
|
if (allGoingOff) {
|
||||||
|
await subscript.unsubscribe();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logConsoleAndDb("Subscription object is not available.");
|
logConsoleAndDb("Subscription object is not available.");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -348,15 +360,20 @@ export default class App extends Vue {
|
|||||||
"Push provider server communication failed: " + JSON.stringify(error),
|
"Push provider server communication failed: " + JSON.stringify(error),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const pushServerSuccess: boolean = await fetch("/web-push/unsubscribe", {
|
const serverSubscription = {
|
||||||
|
...subscription,
|
||||||
|
};
|
||||||
|
if (!allGoingOff) {
|
||||||
|
serverSubscription["notifyType"] = notification.title;
|
||||||
|
}
|
||||||
|
const pushServerSuccess = await fetch("/web-push/unsubscribe", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(subscription),
|
body: JSON.stringify(serverSubscription),
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response.ok;
|
return response.ok;
|
||||||
@@ -370,34 +387,24 @@ export default class App extends Vue {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let message;
|
let message;
|
||||||
if (pushProviderSuccess === pushServerSuccess) {
|
if (pushServerSuccess) {
|
||||||
message = "Both local and server notifications ";
|
message = "Notification is off.";
|
||||||
if (pushProviderSuccess) {
|
|
||||||
message += "are off.";
|
|
||||||
} else {
|
} else {
|
||||||
message += "failed to turn off.";
|
message = "Notification is still on. Try to turn it off again.";
|
||||||
}
|
}
|
||||||
} else {
|
this.$notify(
|
||||||
message =
|
{
|
||||||
"Local unsubscribe " +
|
|
||||||
(pushProviderSuccess ? "succeeded" : "failed") +
|
|
||||||
" but server unsubscribe " +
|
|
||||||
(pushServerSuccess ? "succeeded" : "failed") +
|
|
||||||
".";
|
|
||||||
}
|
|
||||||
this.$notify({
|
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "info",
|
type: "info",
|
||||||
title: "Finished",
|
title: "Finished",
|
||||||
text: message,
|
text: message,
|
||||||
});
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
|
||||||
await db.open();
|
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
|
||||||
notifyingNewActivity: false,
|
|
||||||
});
|
|
||||||
if (notification.callback) {
|
if (notification.callback) {
|
||||||
notification.callback(pushProviderSuccess && pushServerSuccess);
|
// it's OK if the local notifications are still on (especially if the other notification is on)
|
||||||
|
notification.callback(pushServerSuccess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,12 @@
|
|||||||
>
|
>
|
||||||
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
||||||
<p v-if="serviceWorkerReady && vapidKey" class="text-lg mb-4">
|
<p v-if="serviceWorkerReady && vapidKey" class="text-lg mb-4">
|
||||||
Would you like to be notified of new activity once a day?
|
<span v-if="pushType === DAILY_CHECK_TITLE">
|
||||||
|
Would you like to be notified of new activity, up to once a day?
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
Would you like to get a reminder message once a day?
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p v-else class="text-lg mb-4">
|
<p v-else class="text-lg mb-4">
|
||||||
Waiting for system initialization, which may take up to 5 seconds...
|
Waiting for system initialization, which may take up to 5 seconds...
|
||||||
@@ -24,8 +29,30 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div v-if="serviceWorkerReady && vapidKey">
|
<div v-if="serviceWorkerReady && vapidKey">
|
||||||
|
<div v-if="pushType === DAILY_CHECK_TITLE">
|
||||||
|
<span>Yes, send me a message when there is new data for me</span>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<span>Yes, send me this message:</span>
|
||||||
|
<!-- eslint-disable -->
|
||||||
|
<textarea
|
||||||
|
type="text"
|
||||||
|
id="push-message"
|
||||||
|
v-model="messageInput"
|
||||||
|
class="rounded border border-slate-400 mt-2 px-2 py-2 w-full"
|
||||||
|
maxlength="100"
|
||||||
|
></textarea
|
||||||
|
>
|
||||||
|
<!-- eslint-enable -->
|
||||||
|
<span class="w-full flex justify-between text-xs text-slate-500">
|
||||||
|
<span></span>
|
||||||
|
<span>(100 characters max)</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
<span class="flex flex-row justify-center">
|
<span class="flex flex-row justify-center">
|
||||||
<span class="mt-2">Yes, tell me at: </span>
|
<span class="mt-2">... at: </span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
@change="checkHourInput"
|
@change="checkHourInput"
|
||||||
@@ -46,6 +73,7 @@
|
|||||||
<span v-else> PM <fa icon="chevron-up" /> </span>
|
<span v-else> PM <fa icon="chevron-up" /> </span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white mt-2 px-2 py-2 rounded-md"
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white mt-2 px-2 py-2 rounded-md"
|
||||||
@click="
|
@click="
|
||||||
@@ -73,12 +101,7 @@
|
|||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
|
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
|
||||||
import {
|
import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "@/db/index";
|
||||||
db,
|
|
||||||
logConsoleAndDb,
|
|
||||||
retrieveSettingsForActiveAccount,
|
|
||||||
} from "@/db/index";
|
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
|
||||||
import { urlBase64ToUint8Array } from "@/libs/crypto/vc/util";
|
import { urlBase64ToUint8Array } from "@/libs/crypto/vc/util";
|
||||||
import * as libsUtil from "@/libs/util";
|
import * as libsUtil from "@/libs/util";
|
||||||
|
|
||||||
@@ -89,6 +112,7 @@ interface ErrorResponse {
|
|||||||
|
|
||||||
// PushSubscriptionJSON is defined in the Push API https://www.w3.org/TR/push-api/#dom-pushsubscriptionjson
|
// PushSubscriptionJSON is defined in the Push API https://www.w3.org/TR/push-api/#dom-pushsubscriptionjson
|
||||||
interface PushSubscriptionWithTime extends PushSubscriptionJSON {
|
interface PushSubscriptionWithTime extends PushSubscriptionJSON {
|
||||||
|
message?: string;
|
||||||
notifyTime: { utcHour: number; minute: number };
|
notifyTime: { utcHour: number; minute: number };
|
||||||
notifyType: string;
|
notifyType: string;
|
||||||
}
|
}
|
||||||
@@ -115,17 +139,27 @@ export default class PushNotificationPermission extends Vue {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => Promise<() => void>;
|
$notify!: (notification: NotificationIface, timeout?: number) => Promise<() => void>;
|
||||||
|
|
||||||
callback: (success: boolean, time: string) => void = () => {};
|
DAILY_CHECK_TITLE = libsUtil.DAILY_CHECK_TITLE;
|
||||||
|
DIRECT_PUSH_TITLE = libsUtil.DIRECT_PUSH_TITLE;
|
||||||
|
|
||||||
|
callback: (success: boolean, time: string, message?: string) => void =
|
||||||
|
() => {};
|
||||||
hourAm = true;
|
hourAm = true;
|
||||||
hourInput = "8";
|
hourInput = "8";
|
||||||
isVisible = false;
|
isVisible = false;
|
||||||
|
messageInput = "";
|
||||||
minuteInput = "00";
|
minuteInput = "00";
|
||||||
|
pushType = "";
|
||||||
serviceWorkerReady = false;
|
serviceWorkerReady = false;
|
||||||
vapidKey = "";
|
vapidKey = "";
|
||||||
|
|
||||||
async open(callback?: (success: boolean, time: string) => void) {
|
async open(
|
||||||
this.isVisible = true;
|
pushType: string,
|
||||||
|
callback?: (success: boolean, time: string, message?: string) => void,
|
||||||
|
) {
|
||||||
this.callback = callback || this.callback;
|
this.callback = callback || this.callback;
|
||||||
|
this.isVisible = true;
|
||||||
|
this.pushType = pushType;
|
||||||
try {
|
try {
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const settings = await retrieveSettingsForActiveAccount();
|
||||||
let pushUrl = DEFAULT_PUSH_SERVER;
|
let pushUrl = DEFAULT_PUSH_SERVER;
|
||||||
@@ -194,6 +228,18 @@ export default class PushNotificationPermission extends Vue {
|
|||||||
navigator.serviceWorker?.ready.then(() => {
|
navigator.serviceWorker?.ready.then(() => {
|
||||||
this.serviceWorkerReady = true;
|
this.serviceWorkerReady = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.pushType === this.DIRECT_PUSH_TITLE) {
|
||||||
|
this.messageInput =
|
||||||
|
"Just a friendly reminder: click and share some gratitude with the world.";
|
||||||
|
// focus on the message input
|
||||||
|
setTimeout(function () {
|
||||||
|
document.getElementById("push-message")?.focus();
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
// not critical but doesn't make sense in a daily check
|
||||||
|
this.messageInput = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private close() {
|
private close() {
|
||||||
@@ -264,7 +310,7 @@ export default class PushNotificationPermission extends Vue {
|
|||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Browser Notifications Not Supported",
|
title: "Browser Notifications Are Not Supported",
|
||||||
text: "This browser does not support notifications.",
|
text: "This browser does not support notifications.",
|
||||||
},
|
},
|
||||||
3000,
|
3000,
|
||||||
@@ -329,7 +375,7 @@ export default class PushNotificationPermission extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async turnOnNotifications() {
|
private async turnOnNotifications() {
|
||||||
let notifyCloser = () => {};
|
let notifyCloser = () => {};
|
||||||
return this.askPermission()
|
return this.askPermission()
|
||||||
.then((permission) => {
|
.then((permission) => {
|
||||||
@@ -378,7 +424,8 @@ export default class PushNotificationPermission extends Vue {
|
|||||||
|
|
||||||
const subscriptionWithTime: PushSubscriptionWithTime = {
|
const subscriptionWithTime: PushSubscriptionWithTime = {
|
||||||
notifyTime: { utcHour: finalUtcHour, minute: finalUtcMinute },
|
notifyTime: { utcHour: finalUtcHour, minute: finalUtcMinute },
|
||||||
notifyType: "DAILY_CHECK",
|
notifyType: this.pushType,
|
||||||
|
message: this.messageInput,
|
||||||
...subscription.toJSON(),
|
...subscription.toJSON(),
|
||||||
};
|
};
|
||||||
await this.sendSubscriptionToServer(subscriptionWithTime);
|
await this.sendSubscriptionToServer(subscriptionWithTime);
|
||||||
@@ -398,23 +445,21 @@ export default class PushNotificationPermission extends Vue {
|
|||||||
);
|
);
|
||||||
await libsUtil.sendTestThroughPushServer(subscription, true);
|
await libsUtil.sendTestThroughPushServer(subscription, true);
|
||||||
notifyCloser();
|
notifyCloser();
|
||||||
|
setTimeout(() => {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Notifications Turned On",
|
title: "Notification Is On",
|
||||||
text: "Notifications are on. You should see at least one on your device; if not, check the 'Troubleshoot' page.",
|
text: "You should see at least one on your device; if not, check the 'Troubleshoot' link.",
|
||||||
},
|
},
|
||||||
-1,
|
7000,
|
||||||
);
|
);
|
||||||
|
}, 500);
|
||||||
const timeText =
|
const timeText =
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
this.hourInput + ":" + this.minuteInput + " " + (this.hourAm ? "AM" : "PM");
|
this.hourInput + ":" + this.minuteInput + " " + (this.hourAm ? "AM" : "PM");
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
this.callback(true, timeText, this.messageInput);
|
||||||
notifyingNewActivity: true,
|
|
||||||
notifyingNewActivityTime: timeText,
|
|
||||||
});
|
|
||||||
this.callback(true, timeText);
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logConsoleAndDb(
|
logConsoleAndDb(
|
||||||
@@ -434,14 +479,7 @@ export default class PushNotificationPermission extends Vue {
|
|||||||
},
|
},
|
||||||
3000,
|
3000,
|
||||||
);
|
);
|
||||||
// unsubscribe just in case we failed after getting a subscription
|
// if we want to also unsubscribe, be sure to do that only if no other notification is active
|
||||||
navigator.serviceWorker?.ready
|
|
||||||
.then((registration) => registration.pushManager.getSubscription())
|
|
||||||
.then((subscription) => {
|
|
||||||
if (subscription) {
|
|
||||||
subscription.unsubscribe();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export enum AppString {
|
|||||||
TEST_IMAGE_API_SERVER = "https://test-image-api.timesafari.app",
|
TEST_IMAGE_API_SERVER = "https://test-image-api.timesafari.app",
|
||||||
LOCAL_IMAGE_API_SERVER = "http://localhost:3001",
|
LOCAL_IMAGE_API_SERVER = "http://localhost:3001",
|
||||||
|
|
||||||
PROD_PARTNER_API_SERVER = "https://endorser.ch",
|
PROD_PARTNER_API_SERVER = "https://partner-api.endorser.ch",
|
||||||
TEST_PARTNER_API_SERVER = "https://test-partner.endorser.ch",
|
TEST_PARTNER_API_SERVER = "https://test-partner-api.endorser.ch",
|
||||||
LOCAL_PARTNER_API_SERVER = LOCAL_ENDORSER_API_SERVER,
|
LOCAL_PARTNER_API_SERVER = LOCAL_ENDORSER_API_SERVER,
|
||||||
|
|
||||||
PROD_PUSH_SERVER = "https://timesafari.app",
|
PROD_PUSH_SERVER = "https://timesafari.app",
|
||||||
|
|||||||
@@ -39,8 +39,11 @@ export type Settings = {
|
|||||||
lastNotifiedClaimId?: string;
|
lastNotifiedClaimId?: string;
|
||||||
lastViewedClaimId?: string;
|
lastViewedClaimId?: string;
|
||||||
|
|
||||||
notifyingNewActivity?: boolean; // set if they have turned on daily check for new activity via the push server
|
|
||||||
notifyingNewActivityTime?: string; // set to their chosen time if they have turned on daily check for new activity via the push server
|
notifyingNewActivityTime?: string; // set to their chosen time if they have turned on daily check for new activity via the push server
|
||||||
|
notifyingReminderMessage?: string; // set to their chosen message for a daily reminder
|
||||||
|
notifyingReminderTime?: string; // set to their chosen time for a daily reminder
|
||||||
|
|
||||||
|
partnerApiServer?: string; // partner server API URL
|
||||||
|
|
||||||
passkeyExpirationMinutes?: number; // passkey access token time-to-live in minutes
|
passkeyExpirationMinutes?: number; // passkey access token time-to-live in minutes
|
||||||
|
|
||||||
|
|||||||
@@ -364,6 +364,11 @@ export const getPasskeyExpirationSeconds = async (): Promise<number> => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// These are shared with the service worker and should be a constant. Look for the same name in additional-scripts.js
|
||||||
|
export const DAILY_CHECK_TITLE = "DAILY_CHECK";
|
||||||
|
// This is a special value that tells the service worker to send a direct notification to the device, skipping filters.
|
||||||
|
export const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
|
||||||
|
|
||||||
export const sendTestThroughPushServer = async (
|
export const sendTestThroughPushServer = async (
|
||||||
subscriptionJSON: PushSubscriptionJSON,
|
subscriptionJSON: PushSubscriptionJSON,
|
||||||
skipFilter: boolean,
|
skipFilter: boolean,
|
||||||
@@ -374,16 +379,12 @@ export const sendTestThroughPushServer = async (
|
|||||||
pushUrl = settings.webPushServer;
|
pushUrl = settings.webPushServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is shared with the service worker and should be a constant. Look for the same name in additional-scripts.js
|
|
||||||
// This is a special value that tells the service worker to send a direct notification to the device, skipping filters.
|
|
||||||
// Use something other than "Daily Update" https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/3c0e196c11bc98060ec5934e99e7dbd591b5da4d/app.py#L213
|
|
||||||
const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION";
|
|
||||||
|
|
||||||
const newPayload = {
|
const newPayload = {
|
||||||
|
...subscriptionJSON,
|
||||||
|
// ... overridden with the following
|
||||||
// eslint-disable-next-line prettier/prettier
|
// eslint-disable-next-line prettier/prettier
|
||||||
message: `Test, where you will see this message ${ skipFilter ? "un" : "" }filtered.`,
|
message: `Test, where you will see this message ${ skipFilter ? "un" : "" }filtered.`,
|
||||||
title: skipFilter ? DIRECT_PUSH_TITLE : "Your Web Push",
|
title: skipFilter ? DIRECT_PUSH_TITLE : "Your Web Push",
|
||||||
...subscriptionJSON,
|
|
||||||
};
|
};
|
||||||
console.log("Sending a test web push message:", newPayload);
|
console.log("Sending a test web push message:", newPayload);
|
||||||
const payloadStr = JSON.stringify(newPayload);
|
const payloadStr = JSON.stringify(newPayload);
|
||||||
|
|||||||
@@ -184,7 +184,36 @@
|
|||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<!-- label -->
|
<!-- label -->
|
||||||
<div>
|
<div>
|
||||||
New Activity Notifications
|
Reminder Notification
|
||||||
|
<fa
|
||||||
|
icon="question-circle"
|
||||||
|
class="text-slate-400 fa-fw ml-2 cursor-pointer"
|
||||||
|
@click.stop="showReminderNotificationInfo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- toggle -->
|
||||||
|
<div
|
||||||
|
class="relative ml-2 cursor-pointer"
|
||||||
|
@click="showReminderNotificationChoice()"
|
||||||
|
>
|
||||||
|
<!-- input -->
|
||||||
|
<input type="checkbox" v-model="notifyingReminder" class="sr-only" />
|
||||||
|
<!-- line -->
|
||||||
|
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
||||||
|
<!-- dot -->
|
||||||
|
<div
|
||||||
|
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="notifyingReminder" class="w-full flex justify-between">
|
||||||
|
<span class="ml-8 mr-8">Message: "{{ notifyingReminderMessage }}"</span>
|
||||||
|
<span>{{ notifyingReminderTime.replace(" ", " ") }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 flex items-center justify-between">
|
||||||
|
<!-- label -->
|
||||||
|
<div>
|
||||||
|
New Activity Notification
|
||||||
<fa
|
<fa
|
||||||
icon="question-circle"
|
icon="question-circle"
|
||||||
class="text-slate-400 fa-fw ml-2 cursor-pointer"
|
class="text-slate-400 fa-fw ml-2 cursor-pointer"
|
||||||
@@ -200,7 +229,6 @@
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
v-model="notifyingNewActivity"
|
v-model="notifyingNewActivity"
|
||||||
name="toggleNotificationsInput"
|
|
||||||
class="sr-only"
|
class="sr-only"
|
||||||
/>
|
/>
|
||||||
<!-- line -->
|
<!-- line -->
|
||||||
@@ -212,7 +240,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="notifyingNewActivityTime" class="w-full text-right">
|
<div v-if="notifyingNewActivityTime" class="w-full text-right">
|
||||||
{{ notifyingNewActivityTime }}
|
{{ notifyingNewActivityTime.replace(" ", " ") }}
|
||||||
</div>
|
</div>
|
||||||
<router-link class="pl-4 text-sm text-blue-500" to="/help-notifications">
|
<router-link class="pl-4 text-sm text-blue-500" to="/help-notifications">
|
||||||
Troubleshoot your notification setup.
|
Troubleshoot your notification setup.
|
||||||
@@ -609,18 +637,51 @@
|
|||||||
{{ DEFAULT_PUSH_SERVER }}
|
{{ DEFAULT_PUSH_SERVER }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<h2 class="text-slate-500 text-sm font-bold mb-2">Partner Server URL</h2>
|
||||||
|
<div class="px-3 py-4">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="block w-full rounded border border-slate-400 px-3 py-2"
|
||||||
|
v-model="partnerApiServerInput"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
v-if="partnerApiServerInput != partnerApiServer"
|
||||||
|
class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
|
||||||
|
@click="onClickSavePartnerServer()"
|
||||||
|
>
|
||||||
|
<fa icon="floppy-disk" class="fa-fw" color="white"></fa>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||||
|
@click="partnerApiServerInput = AppConstants.PROD_PARTNER_API_SERVER"
|
||||||
|
>
|
||||||
|
Use Prod
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||||
|
@click="partnerApiServerInput = AppConstants.TEST_PARTNER_API_SERVER"
|
||||||
|
>
|
||||||
|
Use Test
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||||
|
@click="partnerApiServerInput = AppConstants.LOCAL_PARTNER_API_SERVER"
|
||||||
|
>
|
||||||
|
Use Local
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span class="px-4 text-sm" v-if="!partnerApiServerInput">
|
||||||
|
When that setting is blank, this app will use the default partner server
|
||||||
|
URL:
|
||||||
|
{{ DEFAULT_PARTNER_API_SERVER }}
|
||||||
|
</span>
|
||||||
|
|
||||||
<div id="sectionImageServerURL" class="mt-2">
|
<div id="sectionImageServerURL" class="mt-2">
|
||||||
<span class="text-slate-500 text-sm font-bold">Image Server URL</span>
|
<span class="text-slate-500 text-sm font-bold">Image Server URL</span>
|
||||||
|
|
||||||
<span class="text-sm">{{ DEFAULT_IMAGE_API_SERVER }}</span>
|
<span class="text-sm">{{ DEFAULT_IMAGE_API_SERVER }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="sectionPartnerServerURL" class="mt-2">
|
|
||||||
<span class="text-slate-500 text-sm font-bold">Partner Server URL</span>
|
|
||||||
|
|
||||||
<span class="text-sm">{{ DEFAULT_PARTNER_API_SERVER }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label
|
<label
|
||||||
for="toggleHideRegisterPromptOnNewContact"
|
for="toggleHideRegisterPromptOnNewContact"
|
||||||
class="flex items-center justify-between cursor-pointer mt-4"
|
class="flex items-center justify-between cursor-pointer mt-4"
|
||||||
@@ -778,7 +839,7 @@ import {
|
|||||||
ImageRateLimits,
|
ImageRateLimits,
|
||||||
tokenExpiryTimeDescription,
|
tokenExpiryTimeDescription,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import { getAccount } from "@/libs/util";
|
import { DAILY_CHECK_TITLE, DIRECT_PUSH_TITLE, getAccount } from "@/libs/util";
|
||||||
|
|
||||||
const inputImportFileNameRef = ref<Blob>();
|
const inputImportFileNameRef = ref<Blob>();
|
||||||
|
|
||||||
@@ -815,6 +876,11 @@ export default class AccountViewView extends Vue {
|
|||||||
loadingLimits = false;
|
loadingLimits = false;
|
||||||
notifyingNewActivity = false;
|
notifyingNewActivity = false;
|
||||||
notifyingNewActivityTime = "";
|
notifyingNewActivityTime = "";
|
||||||
|
notifyingReminder = false;
|
||||||
|
notifyingReminderMessage = "";
|
||||||
|
notifyingReminderTime = "";
|
||||||
|
partnerApiServer = "";
|
||||||
|
partnerApiServerInput = "";
|
||||||
passkeyExpirationDescription = "";
|
passkeyExpirationDescription = "";
|
||||||
passkeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
|
passkeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
|
||||||
previousPasskeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
|
previousPasskeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
|
||||||
@@ -858,7 +924,7 @@ export default class AccountViewView extends Vue {
|
|||||||
const registration = await navigator.serviceWorker?.ready;
|
const registration = await navigator.serviceWorker?.ready;
|
||||||
this.subscription = await registration.pushManager.getSubscription();
|
this.subscription = await registration.pushManager.getSubscription();
|
||||||
if (!this.subscription) {
|
if (!this.subscription) {
|
||||||
if (this.notifyingNewActivity) {
|
if (this.notifyingNewActivity || this.notifyingReminder) {
|
||||||
// the app thought there was a subscription but there isn't, so fix the settings
|
// the app thought there was a subscription but there isn't, so fix the settings
|
||||||
this.turnOffNotifyingFlags();
|
this.turnOffNotifyingFlags();
|
||||||
}
|
}
|
||||||
@@ -909,14 +975,19 @@ export default class AccountViewView extends Vue {
|
|||||||
this.givenName =
|
this.givenName =
|
||||||
(settings?.firstName || "") +
|
(settings?.firstName || "") +
|
||||||
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
|
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
|
||||||
this.isRegistered = !!settings?.isRegistered;
|
|
||||||
this.imageServer = settings.imageServer || "";
|
|
||||||
this.profileImageUrl = settings.profileImageUrl;
|
|
||||||
this.showContactGives = !!settings.showContactGivesInline;
|
|
||||||
this.hideRegisterPromptOnNewContact =
|
this.hideRegisterPromptOnNewContact =
|
||||||
!!settings.hideRegisterPromptOnNewContact;
|
!!settings.hideRegisterPromptOnNewContact;
|
||||||
this.notifyingNewActivity = !!settings.notifyingNewActivity;
|
this.isRegistered = !!settings?.isRegistered;
|
||||||
|
this.imageServer = settings.imageServer || "";
|
||||||
|
this.notifyingNewActivity = !!settings.notifyingNewActivityTime;
|
||||||
this.notifyingNewActivityTime = settings.notifyingNewActivityTime || "";
|
this.notifyingNewActivityTime = settings.notifyingNewActivityTime || "";
|
||||||
|
this.notifyingReminder = !!settings.notifyingReminderTime;
|
||||||
|
this.notifyingReminderMessage = settings.notifyingReminderMessage || "";
|
||||||
|
this.notifyingReminderTime = settings.notifyingReminderTime || "";
|
||||||
|
this.partnerApiServer = settings.partnerApiServer || "";
|
||||||
|
this.partnerApiServerInput = settings.partnerApiServer || "";
|
||||||
|
this.profileImageUrl = settings.profileImageUrl;
|
||||||
|
this.showContactGives = !!settings.showContactGivesInline;
|
||||||
this.passkeyExpirationMinutes =
|
this.passkeyExpirationMinutes =
|
||||||
settings.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES;
|
settings.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES;
|
||||||
this.previousPasskeyExpirationMinutes = this.passkeyExpirationMinutes;
|
this.previousPasskeyExpirationMinutes = this.passkeyExpirationMinutes;
|
||||||
@@ -1025,10 +1096,13 @@ export default class AccountViewView extends Vue {
|
|||||||
if (!this.notifyingNewActivity) {
|
if (!this.notifyingNewActivity) {
|
||||||
(
|
(
|
||||||
this.$refs.pushNotificationPermission as PushNotificationPermission
|
this.$refs.pushNotificationPermission as PushNotificationPermission
|
||||||
).open((success: boolean, time: string) => {
|
).open(DAILY_CHECK_TITLE, async (success: boolean, timeText: string) => {
|
||||||
if (success) {
|
if (success) {
|
||||||
|
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
notifyingNewActivityTime: timeText,
|
||||||
|
});
|
||||||
this.notifyingNewActivity = true;
|
this.notifyingNewActivity = true;
|
||||||
this.notifyingNewActivityTime = time;
|
this.notifyingNewActivityTime = timeText;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -1036,10 +1110,13 @@ export default class AccountViewView extends Vue {
|
|||||||
{
|
{
|
||||||
group: "modal",
|
group: "modal",
|
||||||
type: "notification-off",
|
type: "notification-off",
|
||||||
title: "", // unused, only here to satisfy type check
|
title: DAILY_CHECK_TITLE, // repurposed to indicate the type of notification
|
||||||
text: "", // unused, only here to satisfy type check
|
text: "", // unused, only here to satisfy type check
|
||||||
callback: async (success) => {
|
callback: async (success) => {
|
||||||
if (success) {
|
if (success) {
|
||||||
|
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
notifyingNewActivityTime: "",
|
||||||
|
});
|
||||||
this.notifyingNewActivity = false;
|
this.notifyingNewActivity = false;
|
||||||
this.notifyingNewActivityTime = "";
|
this.notifyingNewActivityTime = "";
|
||||||
}
|
}
|
||||||
@@ -1050,6 +1127,71 @@ export default class AccountViewView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async showReminderNotificationInfo() {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "modal",
|
||||||
|
type: "confirm",
|
||||||
|
title: "Reminder Notification",
|
||||||
|
text: `
|
||||||
|
This will notify you at a specific time each day.
|
||||||
|
Note that it does not give you personalized notifications,
|
||||||
|
so if you want less reliable but personalized notification then choose a 'New Activity' Notification.
|
||||||
|
Do you want more details?
|
||||||
|
`,
|
||||||
|
onYes: async () => {
|
||||||
|
await (this.$router as Router).push({
|
||||||
|
name: "help-notification-types",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
yesText: "tell me more.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async showReminderNotificationChoice() {
|
||||||
|
if (!this.notifyingReminder) {
|
||||||
|
(
|
||||||
|
this.$refs.pushNotificationPermission as PushNotificationPermission
|
||||||
|
).open(
|
||||||
|
DIRECT_PUSH_TITLE,
|
||||||
|
async (success: boolean, timeText: string, message?: string) => {
|
||||||
|
if (success) {
|
||||||
|
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
notifyingReminderMessage: message,
|
||||||
|
notifyingReminderTime: timeText,
|
||||||
|
});
|
||||||
|
this.notifyingReminder = true;
|
||||||
|
this.notifyingReminderMessage = message || "";
|
||||||
|
this.notifyingReminderTime = timeText;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "modal",
|
||||||
|
type: "notification-off",
|
||||||
|
title: DIRECT_PUSH_TITLE, // repurposed to indicate the type of notification
|
||||||
|
text: "", // unused, only here to satisfy type check
|
||||||
|
callback: async (success) => {
|
||||||
|
if (success) {
|
||||||
|
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
notifyingReminderMessage: "",
|
||||||
|
notifyingReminderTime: "",
|
||||||
|
});
|
||||||
|
this.notifyingReminder = false;
|
||||||
|
this.notifyingReminderMessage = "";
|
||||||
|
this.notifyingReminderTime = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async toggleHideRegisterPromptOnNewContact() {
|
public async toggleHideRegisterPromptOnNewContact() {
|
||||||
const newSetting = !this.hideRegisterPromptOnNewContact;
|
const newSetting = !this.hideRegisterPromptOnNewContact;
|
||||||
await db.open();
|
await db.open();
|
||||||
@@ -1069,13 +1211,18 @@ export default class AccountViewView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async turnOffNotifyingFlags() {
|
public async turnOffNotifyingFlags() {
|
||||||
|
// should tell the push server as well
|
||||||
await db.open();
|
await db.open();
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
notifyingNewActivity: false,
|
|
||||||
notifyingNewActivityTime: "",
|
notifyingNewActivityTime: "",
|
||||||
|
notifyingReminderMessage: "",
|
||||||
|
notifyingReminderTime: "",
|
||||||
});
|
});
|
||||||
this.notifyingNewActivity = false;
|
this.notifyingNewActivity = false;
|
||||||
this.notifyingNewActivityTime = "";
|
this.notifyingNewActivityTime = "";
|
||||||
|
this.notifyingReminder = false;
|
||||||
|
this.notifyingReminderMessage = "";
|
||||||
|
this.notifyingReminderTime = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1424,6 +1571,14 @@ export default class AccountViewView extends Vue {
|
|||||||
this.apiServer = this.apiServerInput;
|
this.apiServer = this.apiServerInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onClickSavePartnerServer() {
|
||||||
|
await db.open();
|
||||||
|
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
partnerApiServer: this.partnerApiServerInput,
|
||||||
|
});
|
||||||
|
this.partnerApiServer = this.partnerApiServerInput;
|
||||||
|
}
|
||||||
|
|
||||||
async onClickSavePushServer() {
|
async onClickSavePushServer() {
|
||||||
await db.open();
|
await db.open();
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<div>
|
<div>
|
||||||
<fa icon="arrow-down" class="fa-fw text-slate-400" />
|
<fa icon="arrow-left" class="fa-fw text-slate-400" />
|
||||||
{{ giverName }}
|
{{ giverName }}
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-6">gave</div>
|
<div class="ml-6">gave</div>
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="ml-6">to</div>
|
<div class="ml-6">to</div>
|
||||||
<div>
|
<div>
|
||||||
<fa icon="arrow-up" class="fa-fw text-slate-400" />
|
<fa icon="arrow-right" class="fa-fw text-slate-400" />
|
||||||
{{ recipientName }}
|
{{ recipientName }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -39,6 +39,15 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h2 class="text-xl font-semibold mt-4">Android Users</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Note that you may not receive notifications when the app is in the
|
||||||
|
background. When you're done working, close the app, and then you'll
|
||||||
|
get the reminder notifications.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2 class="text-xl font-semibold mt-4">
|
<h2 class="text-xl font-semibold mt-4">
|
||||||
If this app doesn't support notifications...
|
If this app doesn't support notifications...
|
||||||
<!-- Note that that exact verbiage shows in a message elsewhere. -->
|
<!-- Note that that exact verbiage shows in a message elsewhere. -->
|
||||||
|
|||||||
@@ -151,10 +151,12 @@
|
|||||||
<input type="checkbox" class="mr-2" v-model="sendToTrustroots" />
|
<input type="checkbox" class="mr-2" v-model="sendToTrustroots" />
|
||||||
<label>Send to Trustroots</label>
|
<label>Send to Trustroots</label>
|
||||||
</div>
|
</div>
|
||||||
|
<!--
|
||||||
<div class="flex" @click="sendToTripHopping = !sendToTripHopping">
|
<div class="flex" @click="sendToTripHopping = !sendToTripHopping">
|
||||||
<input type="checkbox" class="mr-2" v-model="sendToTripHopping" />
|
<input type="checkbox" class="mr-2" v-model="sendToTripHopping" />
|
||||||
<label>Send to TripHopping</label>
|
<label>Send to TripHopping</label>
|
||||||
</div>
|
</div>
|
||||||
|
-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
@@ -581,7 +583,12 @@ export default class NewEditProjectView extends Vue {
|
|||||||
);
|
);
|
||||||
const nostrPubKey = pubPri?.publicKey;
|
const nostrPubKey = pubPri?.publicKey;
|
||||||
|
|
||||||
const trustrootsUrl = DEFAULT_PARTNER_API_SERVER + "/api/partner/link";
|
let partnerServer = DEFAULT_PARTNER_API_SERVER;
|
||||||
|
const settings = await retrieveSettingsForActiveAccount();
|
||||||
|
if (settings.partnerApiServer) {
|
||||||
|
partnerServer = settings.partnerApiServer;
|
||||||
|
}
|
||||||
|
const trustrootsUrl = partnerServer + "/api/partner/link";
|
||||||
const timeSafariUrl = window.location.origin + "/claim/" + jwtId;
|
const timeSafariUrl = window.location.origin + "/claim/" + jwtId;
|
||||||
const content = this.fullClaim.name + " - see " + timeSafariUrl;
|
const content = this.fullClaim.name + " - see " + timeSafariUrl;
|
||||||
// Why does IntelliJ not see matching types?
|
// Why does IntelliJ not see matching types?
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ self.addEventListener("push", function (event) {
|
|||||||
let message = "Got some empty message.";
|
let message = "Got some empty message.";
|
||||||
if (payload && payload.title == DIRECT_PUSH_TITLE) {
|
if (payload && payload.title == DIRECT_PUSH_TITLE) {
|
||||||
// skip any search logic and show the message directly
|
// skip any search logic and show the message directly
|
||||||
title = "Direct Notification";
|
title = "Direct Message";
|
||||||
message = payload.message || "No details were provided.";
|
message = payload.message || "No details were provided.";
|
||||||
} else {
|
} else {
|
||||||
// any other title will run through regular filtering logic
|
// any other title will run through regular filtering logic
|
||||||
|
|||||||
8
test-playwright/LICENSE
Normal file
8
test-playwright/LICENSE
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
The author disclaims copyright to this source code. In place of a legal notice, here is a blessing:
|
||||||
|
|
||||||
|
May you do good and not evil.
|
||||||
|
May you find forgiveness for yourself and forgive others.
|
||||||
|
May you share freely, never taking more than you give.
|
||||||
|
|
||||||
|
________________________________________________________________
|
||||||
|
from https://www.sqlite.org/src/info/689401a6cfb4c234 and memorialized here https://spdx.org/licenses/blessing.html
|
||||||
Reference in New Issue
Block a user