Merge branch 'master' into vue-cli-3

bringing in the changes for v0.9.8
This commit is contained in:
Daniel J. Summers
2018-08-18 21:38:16 -05:00
32 changed files with 1238 additions and 866 deletions

View File

@@ -88,7 +88,7 @@ module Entities =
m.Property(fun e -> e.notes).IsRequired () |> ignore)
|> ignore
// Request is the identifying record for a prayer request.
/// Request is the identifying record for a prayer request
and [<CLIMutable; NoComparison; NoEquality>] Request =
{ /// The ID of the request
requestId : RequestId
@@ -96,8 +96,14 @@ module Entities =
enteredOn : int64
/// The ID of the user to whom this request belongs ("sub" from the JWT)
userId : string
/// The time that this request should reappear in the user's journal
/// The time at which this request should reappear in the user's journal by manual user choice
snoozedUntil : int64
/// The time at which this request should reappear in the user's journal by recurrence
showAfter : int64
/// The type of recurrence for this request
recurType : string
/// How many of the recurrence intervals should occur between appearances in the journal
recurCount : int16
/// The history entries for this request
history : ICollection<History>
/// The notes for this request
@@ -110,6 +116,9 @@ module Entities =
enteredOn = 0L
userId = ""
snoozedUntil = 0L
showAfter = 0L
recurType = "immediate"
recurCount = 0s
history = List<History> ()
notes = List<Note> ()
}
@@ -123,6 +132,9 @@ module Entities =
m.Property(fun e -> e.enteredOn).IsRequired () |> ignore
m.Property(fun e -> e.userId).IsRequired () |> ignore
m.Property(fun e -> e.snoozedUntil).IsRequired () |> ignore
m.Property(fun e -> e.showAfter).IsRequired () |> ignore
m.Property(fun e -> e.recurType).IsRequired() |> ignore
m.Property(fun e -> e.recurCount).IsRequired() |> ignore
m.HasMany(fun e -> e.history :> IEnumerable<History>)
.WithOne()
.HasForeignKey(fun e -> e.requestId :> obj)
@@ -149,6 +161,12 @@ module Entities =
lastStatus : string
/// The time that this request should reappear in the user's journal
snoozedUntil : int64
/// The time after which this request should reappear in the user's journal by configured recurrence
showAfter : int64
/// The type of recurrence for this request
recurType : string
/// How many of the recurrence intervals should occur between appearances in the journal
recurCount : int16
/// History entries for the request
history : History list
/// Note entries for the request
@@ -227,7 +245,7 @@ type AppDbContext (opts : DbContextOptions<AppDbContext>) =
.OrderBy(fun r -> r.asOf)
/// Retrieve a request by its ID and user ID
member this.TryRequestById reqId userId : Task<Request option> =
member this.TryRequestById reqId userId =
task {
let! req = this.Requests.AsNoTracking().FirstOrDefaultAsync(fun r -> r.requestId = reqId && r.userId = userId)
return toOption req
@@ -236,8 +254,7 @@ type AppDbContext (opts : DbContextOptions<AppDbContext>) =
/// Retrieve notes for a request by its ID and user ID
member this.NotesById reqId userId =
task {
let! req = this.TryRequestById reqId userId
match req with
match! this.TryRequestById reqId userId with
| Some _ -> return this.Notes.AsNoTracking().Where(fun n -> n.requestId = reqId) |> List.ofSeq
| None -> return []
}
@@ -250,34 +267,17 @@ type AppDbContext (opts : DbContextOptions<AppDbContext>) =
}
/// Retrieve a request, including its history and notes, by its ID and user ID
member this.TryCompleteRequestById requestId userId =
member this.TryFullRequestById requestId userId =
task {
let! req = this.TryJournalById requestId userId
match req with
| Some r ->
match! this.TryJournalById requestId userId with
| Some req ->
let! fullReq =
this.Requests.AsNoTracking()
.Include(fun r -> r.history)
.Include(fun r -> r.notes)
.FirstOrDefaultAsync(fun r -> r.requestId = requestId && r.userId = userId)
match toOption fullReq with
| Some _ -> return Some { r with history = List.ofSeq fullReq.history; notes = List.ofSeq fullReq.notes }
| None -> return None
| None -> return None
}
/// Retrieve a request, including its history, by its ID and user ID
member this.TryFullRequestById requestId userId =
task {
let! req = this.TryJournalById requestId userId
match req with
| Some r ->
let! fullReq =
this.Requests.AsNoTracking()
.Include(fun r -> r.history)
.FirstOrDefaultAsync(fun r -> r.requestId = requestId && r.userId = userId)
match toOption fullReq with
| Some _ -> return Some { r with history = List.ofSeq fullReq.history }
| Some _ -> return Some { req with history = List.ofSeq fullReq.history; notes = List.ofSeq fullReq.notes }
| None -> return None
| None -> return None
}

View File

@@ -6,6 +6,14 @@ open Giraffe
open MyPrayerJournal
open System
/// Handler to return Vue files
module Vue =
/// The application index page
let app : HttpHandler = htmlFile "wwwroot/index.html"
/// Handlers for error conditions
module Error =
open Microsoft.Extensions.Logging
@@ -18,12 +26,12 @@ module Error =
/// Handle 404s from the API, sending known URL paths to the Vue app so that they can be handled there
let notFound : HttpHandler =
fun next ctx ->
[ "/answered"; "/journal"; "/snoozed"; "/user" ]
[ "/journal"; "/legal"; "/request"; "/user" ]
|> List.filter ctx.Request.Path.Value.StartsWith
|> List.length
|> function
| 0 -> (setStatusCode 404 >=> json ([ "error", "not found" ] |> dict)) next ctx
| _ -> htmlFile "wwwroot/index.html" next ctx
| _ -> Vue.app next ctx
/// Handler helpers
@@ -87,13 +95,33 @@ module Models =
notes : string
}
/// Recurrence update
[<CLIMutable>]
type Recurrence =
{ /// The recurrence type
recurType : string
/// The recurrence cound
recurCount : int16
}
/// A prayer request
[<CLIMutable>]
type Request =
{ /// The text of the request
requestText : string
/// The recurrence type
recurType : string
/// The recurrence count
recurCount : int16
}
/// Reset the "showAfter" property on a request
[<CLIMutable>]
type Show =
{ /// The time after which the request should appear
showAfter : int64
}
/// The time until which a request should not appear in the journal
[<CLIMutable>]
type SnoozeUntil =
@@ -119,6 +147,15 @@ module Request =
open NCuid
/// Ticks per recurrence
let private recurrence =
[ "immediate", 0L
"hours", 3600000L
"days", 86400000L
"weeks", 604800000L
]
|> Map.ofList
/// POST /api/request
let add : HttpHandler =
authorize
@@ -130,10 +167,12 @@ module Request =
let usrId = userId ctx
let now = jsNow ()
{ Request.empty with
requestId = reqId
userId = usrId
enteredOn = now
snoozedUntil = 0L
requestId = reqId
userId = usrId
enteredOn = now
showAfter = now
recurType = r.recurType
recurCount = r.recurCount
}
|> db.AddEntry
{ History.empty with
@@ -144,9 +183,8 @@ module Request =
}
|> db.AddEntry
let! _ = db.SaveChangesAsync ()
let! req = db.TryJournalById reqId usrId
match req with
| Some rqst -> return! (setStatusCode 201 >=> json rqst) next ctx
match! db.TryJournalById reqId usrId with
| Some req -> return! (setStatusCode 201 >=> json req) next ctx
| None -> return! Error.notFound next ctx
}
@@ -155,18 +193,22 @@ module Request =
authorize
>=> fun next ctx ->
task {
let db = db ctx
let! req = db.TryRequestById reqId (userId ctx)
match req with
| Some _ ->
let db = db ctx
match! db.TryRequestById reqId (userId ctx) with
| Some req ->
let! hist = ctx.BindJsonAsync<Models.HistoryEntry> ()
let now = jsNow ()
{ History.empty with
requestId = reqId
asOf = jsNow ()
asOf = now
status = hist.status
text = match hist.updateText with null | "" -> None | x -> Some x
}
|> db.AddEntry
match hist.status with
| "Prayed" ->
db.UpdateEntry { req with showAfter = now + (recurrence.[req.recurType] * int64 req.recurCount) }
| _ -> ()
let! _ = db.SaveChangesAsync ()
return! created next ctx
| None -> return! Error.notFound next ctx
@@ -177,15 +219,14 @@ module Request =
authorize
>=> fun next ctx ->
task {
let db = db ctx
let! req = db.TryRequestById reqId (userId ctx)
match req with
let db = db ctx
match! db.TryRequestById reqId (userId ctx) with
| Some _ ->
let! notes = ctx.BindJsonAsync<Models.NoteEntry> ()
{ Note.empty with
requestId = reqId
asOf = jsNow ()
notes = notes.notes
asOf = jsNow ()
notes = notes.notes
}
|> db.AddEntry
let! _ = db.SaveChangesAsync ()
@@ -206,20 +247,8 @@ module Request =
authorize
>=> fun next ctx ->
task {
let! req = (db ctx).TryJournalById reqId (userId ctx)
match req with
| Some r -> return! json r next ctx
| None -> return! Error.notFound next ctx
}
/// GET /api/request/[req-id]/complete
let getComplete reqId : HttpHandler =
authorize
>=> fun next ctx ->
task {
let! req = (db ctx).TryCompleteRequestById reqId (userId ctx)
match req with
| Some r -> return! json r next ctx
match! (db ctx).TryJournalById reqId (userId ctx) with
| Some req -> return! json req next ctx
| None -> return! Error.notFound next ctx
}
@@ -228,9 +257,8 @@ module Request =
authorize
>=> fun next ctx ->
task {
let! req = (db ctx).TryFullRequestById reqId (userId ctx)
match req with
| Some r -> return! json r next ctx
match! (db ctx).TryFullRequestById reqId (userId ctx) with
| Some req -> return! json req next ctx
| None -> return! Error.notFound next ctx
}
@@ -243,17 +271,48 @@ module Request =
return! json notes next ctx
}
/// POST /api/request/[req-id]/snooze
let snooze reqId : HttpHandler =
/// PATCH /api/request/[req-id]/show
let show reqId : HttpHandler =
authorize
>=> fun next ctx ->
task {
let db = db ctx
let! req = db.TryRequestById reqId (userId ctx)
match req with
| Some r ->
let! until = ctx.BindJsonAsync<Models.SnoozeUntil> ()
{ r with snoozedUntil = until.until }
let db = db ctx
match! db.TryRequestById reqId (userId ctx) with
| Some req ->
let! show = ctx.BindJsonAsync<Models.Show> ()
{ req with showAfter = show.showAfter }
|> db.UpdateEntry
let! _ = db.SaveChangesAsync ()
return! setStatusCode 204 next ctx
| None -> return! Error.notFound next ctx
}
/// PATCH /api/request/[req-id]/snooze
let snooze reqId : HttpHandler =
authorize
>=> fun next ctx ->
task {
let db = db ctx
match! db.TryRequestById reqId (userId ctx) with
| Some req ->
let! until = ctx.BindJsonAsync<Models.SnoozeUntil> ()
{ req with snoozedUntil = until.until; showAfter = until.until }
|> db.UpdateEntry
let! _ = db.SaveChangesAsync ()
return! setStatusCode 204 next ctx
| None -> return! Error.notFound next ctx
}
/// PATCH /api/request/[req-id]/recurrence
let updateRecurrence reqId : HttpHandler =
authorize
>=> fun next ctx ->
task {
let db = db ctx
match! db.TryRequestById reqId (userId ctx) with
| Some req ->
let! recur = ctx.BindJsonAsync<Models.Recurrence> ()
{ req with recurType = recur.recurType; recurCount = recur.recurCount }
|> db.UpdateEntry
let! _ = db.SaveChangesAsync ()
return! setStatusCode 204 next ctx

View File

@@ -53,16 +53,22 @@ module Configure =
/// Routes for the available URLs within myPrayerJournal
let webApp =
router Handlers.Error.notFound [
route "/" (htmlFile "wwwroot/index.html")
route "/" Handlers.Vue.app
subRoute "/api/" [
GET [
route "journal" Handlers.Journal.journal
subRoute "request" [
route "s/answered" Handlers.Request.answered
routef "/%s/complete" Handlers.Request.getComplete
routef "/%s/full" Handlers.Request.getFull
routef "/%s/notes" Handlers.Request.getNotes
routef "/%s" Handlers.Request.get
route "s/answered" Handlers.Request.answered
routef "/%s/full" Handlers.Request.getFull
routef "/%s/notes" Handlers.Request.getNotes
routef "/%s" Handlers.Request.get
]
]
PATCH [
subRoute "request" [
routef "/%s/recurrence" Handlers.Request.updateRecurrence
routef "/%s/show" Handlers.Request.show
routef "/%s/snooze" Handlers.Request.snooze
]
]
POST [
@@ -70,7 +76,6 @@ module Configure =
route "" Handlers.Request.add
routef "/%s/history" Handlers.Request.addHistory
routef "/%s/note" Handlers.Request.addNote
routef "/%s/snooze" Handlers.Request.snooze
]
]
]

View File

@@ -17,14 +17,11 @@
"dependencies": {
"auth0-js": "^9.7.3",
"axios": "^0.18.0",
"bootstrap": "^4.1.3",
"bootstrap-vue": "^2.0.0-rc.11",
"moment": "^2.22.2",
"pug": "^2.0.3",
"vue": "^2.5.17",
"vue-awesome": "^2.3.3",
"vue-progressbar": "^0.7.5",
"vue-router": "^3.0.1",
"moment": "^2.18.1",
"pug": "^2.0.1",
"vue": "^2.5.15",
"vue-progressbar": "^0.7.3",
"vue-router": "^3.0.0",
"vue-toast": "^3.1.0",
"vuex": "^3.0.1"
},

View File

@@ -1,25 +1,28 @@
<template lang="pug">
#app
#app(role='application')
navigation
#content.container
#content
router-view
vue-progress-bar
toast(ref='toast')
footer
p.text-right.text-muted
footer.mpj-text-right.mpj-muted-text
p
| myPrayerJournal v{{ version }}
br
em: small.
#[router-link(:to="{ name: 'PrivacyPolicy' }") Privacy Policy] &bull;
#[router-link(:to="{ name: 'TermsOfService' }") Terms of Service] &bull;
#[a(href='https://github.com/bit-badger/myprayerjournal') Developed] and hosted by
#[a(href='https://bitbadger.solutions') Bit Badger Solutions]
#[a(href='https://github.com/bit-badger/myprayerjournal' target='_blank') Developed] and hosted by
#[a(href='https://bitbadger.solutions' target='_blank') Bit Badger Solutions]
</template>
<script>
'use strict'
import Navigation from './components/Navigation.vue'
import Navigation from './components/common/Navigation.vue'
import { version } from '../package.json'
export default {
name: 'app',
components: {
@@ -42,9 +45,76 @@ export default {
<style>
html, body {
background-color: whitesmoke;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
font-size: 1rem;
}
body {
padding-top: 60px;
padding-top: 50px;
margin: 0;
}
h1, h2, h3, h4, h5 {
font-weight: 500;
margin-top: 0;
}
h1 {
font-size: 2.5rem;
}
h2 {
font-size: 2rem;
}
h3 {
font-size: 1.75rem;
}
h4 {
font-size: 1.5rem;
}
h5 {
font-size: 1.25rem;
}
p {
margin-bottom: 0;
}
input, textarea, select {
border-radius: .25rem;
font-size: 1rem;
}
textarea {
font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;
}
input, select {
font-family: inherit;
}
button,
a[role="button"] {
border: solid 1px #050;
border-radius: .5rem;
background-color: rgb(235, 235, 235);
padding: .25rem;
font-size: 1rem;
}
a[role="button"]:link,
a[role="button"]:visited {
color: black;
}
button.primary,
a[role="button"].primary {
background-color: white;
border-width: 3px;
}
button:hover,
a[role="button"]:hover {
cursor: pointer;
background-color: #050;
color: white;
text-decoration: none;
}
label {
font-variant: small-caps;
font-size: 1.1rem;
}
label.normal {
font-variant: unset;
font-size: unset;
}
footer {
border-top: solid 1px lightgray;
@@ -56,14 +126,116 @@ footer p {
}
a:link, a:visited {
color: #050;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.mpj-main-content {
max-width: 60rem;
margin: auto;
}
.mpj-main-content-wide {
margin: .5rem;
}
@media screen and (max-width: 21rem) {
.mpj-main-content-wide {
margin: 0;
}
}
.mpj-request-text {
white-space: pre-line;
}
.bg-mpj {
.mpj-request-list p {
border-top: solid 1px lightgray;
}
.mpj-request-list p:first-child {
border-top: none;
}
.mpj-request-log {
width: 100%;
}
.mpj-request-log thead th {
border-top: solid 1px lightgray;
border-bottom: solid 2px lightgray;
text-align: left;
}
.mpj-request-log tbody td {
border-bottom: dotted 1px lightgray;
vertical-align: top;
}
.mpj-bg {
background-image: -webkit-gradient(linear, left top, left bottom, from(#050), to(whitesmoke));
background-image: -webkit-linear-gradient(top, #050, whitesmoke);
background-image: -moz-linear-gradient(top, #050, whitesmoke);
background-image: linear-gradient(to bottom, #050, whitesmoke);
}
</style>
.mpj-text-center {
text-align: center;
}
.mpj-text-nowrap {
white-space: nowrap;
}
.mpj-text-right {
text-align: right;
}
.mpj-muted-text {
color: rgba(0, 0, 0, .6);
}
.mpj-narrow {
max-width: 40rem;
margin: auto;
}
.mpj-skinny {
max-width: 20rem;
margin: auto;
}
.mpj-full-width {
width: 100%;
}
.mpj-modal {
position: fixed;
z-index: 8;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, .4);
}
.mpj-modal-content {
background-color: whitesmoke;
border: solid 1px #050;
border-radius: .5rem;
animation-name: animatetop;
animation-duration: 0.4s;
padding: 1rem;
margin-top: 4rem;
}
@keyframes animatetop {
from {
top: -300px;
opacity: 0;
}
to {
top: 0;
opacity: 1;
}
}
.mpj-modal-content header {
margin: -1rem -1rem .5rem;
border-radius: .4rem;
}
.mpj-modal-content header h5 {
color: white;
margin: 0;
padding: 1rem;
}
.mpj-margin {
margin-left: 1rem;
margin-right: 1rem;
}
.material-icons {
vertical-align: middle;
}
</style>

View File

@@ -1,3 +1,5 @@
'use strict'
import axios from 'axios'
const http = axios.create({
@@ -30,8 +32,10 @@ export default {
/**
* Add a new prayer request
* @param {string} requestText The text of the request to be added
* @param {string} recurType The type of recurrence for this request
* @param {number} recurCount The number of intervals of recurrence
*/
addRequest: requestText => http.post('request', { requestText }),
addRequest: (requestText, recurType, recurCount) => http.post('request', { requestText, recurType, recurCount }),
/**
* Get all answered requests, along with the text they had when it was answered
@@ -39,7 +43,7 @@ export default {
getAnsweredRequests: () => http.get('requests/answered'),
/**
* Get a prayer request (full; includes all history)
* Get a prayer request (full; includes all history and notes)
* @param {string} requestId The Id of the request to retrieve
*/
getFullRequest: requestId => http.get(`request/${requestId}/full`),
@@ -56,30 +60,39 @@ export default {
*/
getRequest: requestId => http.get(`request/${requestId}`),
/**
* Get a complete request; equivalent of "full" and "notes" combined
*/
getRequestComplete: requestId => http.get(`request/${requestId}/complete`),
/**
* Get all prayer requests and their most recent updates
*/
journal: () => http.get('journal'),
/**
* Snooze a request until the given time
* @param requestId {string} The ID of the prayer request to be snoozed
* @param until {number} The ticks until which the request should be snoozed
* Show a request after the given date (used for "show now")
* @param {string} requestId The ID of the request which should be shown
* @param {number} showAfter The ticks after which the request should be shown
*/
snoozeRequest: (requestId, until) => http.post(`request/${requestId}/snooze`, { until }),
showRequest: (requestId, showAfter) => http.patch(`request/${requestId}/show`, { showAfter }),
/**
* Snooze a request until the given time
* @param {string} requestId The ID of the prayer request to be snoozed
* @param {number} until The ticks until which the request should be snoozed
*/
snoozeRequest: (requestId, until) => http.patch(`request/${requestId}/snooze`, { until }),
/**
* Update recurrence for a prayer request
* @param {string} requestId The ID of the prayer request for which recurrence is being updated
* @param {string} recurType The type of recurrence to set
* @param {number} recurCount The number of recurrence intervals to set
*/
updateRecurrence: (requestId, recurType, recurCount) =>
http.patch(`request/${requestId}/recurrence`, { recurType, recurCount }),
/**
* Update a prayer request
* @param request The request (should have requestId, status, and updateText properties)
* @param {string} requestId The ID of the request to be updated
* @param {string} status The status of the update
* @param {string} updateText The text of the update (optional)
*/
updateRequest: request => http.post(`request/${request.requestId}/history`, {
status: request.status,
updateText: request.updateText
})
updateRequest: (requestId, status, updateText) => http.post(`request/${requestId}/history`, { status, updateText })
}

View File

@@ -1,83 +0,0 @@
<template lang="pug">
article
page-title(title='Answered Request')
p(v-if='!request') Loading request...
template(v-if='request')
p.
Answered {{ formatDate(answered) }} (#[date-from-now(:value='answered')]) &nbsp;
#[small: em.text-muted prayed {{ prayedCount }} times, open {{ openDays }} days]
p.mpj-request-text {{ lastText }}
b-table(small hover :fields='fields' :items='log')
template(slot='action' scope='data').
{{ data.item.status }} on #[span.text-nowrap {{ formatDate(data.item.asOf) }}]
template(slot='text' scope='data' v-if='data.item.text') {{ data.item.text.fields[0] }}
</template>
<script>
'use strict'
import moment from 'moment'
import api from '@/api'
const asOfDesc = (a, b) => b.asOf - a.asOf
export default {
name: 'answer-detail',
props: {
id: {
type: String,
required: true
}
},
data () {
return {
request: null,
fields: [
{ key: 'action', label: 'Action' },
{ key: 'text', label: 'Update / Notes' }
]
}
},
computed: {
answered () {
return this.request.history.find(hist => hist.status === 'Answered').asOf
},
lastText () {
return this.request.history
.filter(hist => hist.text)
.sort(asOfDesc)[0].text.fields[0]
},
log () {
return (this.request.notes || [])
.map(note => ({ asOf: note.asOf, text: { case: 'Some', fields: [ note.notes ] }, status: 'Notes' }))
.concat(this.request.history)
.sort(asOfDesc)
.slice(1)
},
openDays () {
return Math.floor(
(this.answered - this.request.history.find(hist => hist.status === 'Created').asOf) / 1000 / 60 / 60 / 24)
},
prayedCount () {
return this.request.history.filter(hist => hist.status === 'Prayed').length
}
},
async mounted () {
this.$Progress.start()
try {
const req = await api.getRequestComplete(this.id)
this.request = req.data
this.$Progress.finish()
} catch (e) {
console.log(e)
this.$Progress.fail()
}
},
methods: {
formatDate (asOf) {
return moment(asOf).format('LL')
}
}
}
</script>

View File

@@ -1,5 +1,5 @@
<template lang="pug">
article
article.mpj-main-content(role='main')
page-title(title='Welcome!'
hideOnPage='true')
p &nbsp;

View File

@@ -1,23 +1,23 @@
<template lang="pug">
article
article.mpj-main-content-wide(role='main')
page-title(:title='title')
p(v-if='isLoadingJournal') Loading your prayer journal...
template(v-if='!isLoadingJournal')
new-request
template(v-else)
.mpj-text-center
router-link(:to="{ name: 'EditRequest', params: { id: 'new' } }"
role='button').
#[md-icon(icon='add_box')] Add a New Request
br
b-row(v-if='journal.length > 0')
.mpj-journal(v-if='journal.length > 0')
request-card(v-for='request in journal'
:key='request.requestId'
:request='request'
:events='eventBus'
:toast='toast')
p.text-center(v-if='journal.length === 0'): em.
p.text-center(v-else): em.
No requests found; click the &ldquo;Add a New Request&rdquo; button to add one
edit-request(:events='eventBus'
:toast='toast')
notes-edit(:events='eventBus'
:toast='toast')
full-request(:events='eventBus')
snooze-request(:events='eventBus'
:toast='toast')
</template>
@@ -28,9 +28,6 @@ article
import Vue from 'vue'
import { mapState } from 'vuex'
import EditRequest from './request/EditRequest'
import FullRequest from './request/FullRequest'
import NewRequest from './request/NewRequest'
import NotesEdit from './request/NotesEdit'
import RequestCard from './request/RequestCard'
import SnoozeRequest from './request/SnoozeRequest'
@@ -40,9 +37,6 @@ import actions from '@/store/action-types'
export default {
name: 'journal',
components: {
EditRequest,
FullRequest,
NewRequest,
NotesEdit,
RequestCard,
SnoozeRequest
@@ -67,3 +61,12 @@ export default {
}
}
</script>
<style>
.mpj-journal {
display: flex;
flex-flow: row wrap;
justify-content: center;
align-items: flex-start;
}
</style>

View File

@@ -1,56 +0,0 @@
<template lang="pug">
b-navbar(toggleable='sm'
type='dark'
variant='mpj'
fixed='top')
b-nav-toggle(target='nav_collapse')
b-navbar-brand(to='/')
span(style='font-weight:100;') my
span(style='font-weight:600;') Prayer
span(style='font-weight:700;') Journal
b-collapse#nav_collapse(is-nav)
b-navbar-nav
b-nav-item(v-if='isAuthenticated'
to='/journal') Journal
b-nav-item(v-if='hasSnoozed'
to='/snoozed') Snoozed
b-nav-item(v-if='isAuthenticated'
to='/answered') Answered
b-nav-item(v-if='isAuthenticated'): a(@click.stop='logOff()') Log Off
b-nav-item(v-if='!isAuthenticated'): a(@click.stop='logOn()') Log On
b-nav-item(href='https://bit-badger.github.io/myPrayerJournal/'
target='_blank'
@click.stop='') Docs
</template>
<script>
'use strict'
import { mapState } from 'vuex'
import AuthService from '@/auth/AuthService'
export default {
name: 'navigation',
data () {
return {
auth0: new AuthService()
}
},
computed: {
hasSnoozed () {
return this.isAuthenticated &&
Array.isArray(this.journal) &&
this.journal.filter(req => req.snoozedUntil > Date.now()).length > 0
},
...mapState([ 'journal', 'isAuthenticated' ])
},
methods: {
logOn () {
this.auth0.login()
},
logOff () {
this.auth0.logout(this.$store, this.$router)
}
}
}
</script>

View File

@@ -1,76 +0,0 @@
<template lang="pug">
article
page-title(title='Snoozed Requests')
p(v-if='!loaded') Loading journal...
div(v-if='loaded').mpj-snoozed-list
p.text-center(v-if='requests.length === 0'): em.
No snoozed requests found; return to #[router-link(:to='{ name: "Journal" } ') your journal]
p.mpj-snoozed-text(v-for='req in requests' :key='req.requestId')
| {{ req.text }}
br
br
b-btn(@click='cancelSnooze(req.requestId)'
size='sm'
variant='outline-secondary')
icon(name='times')
= ' Cancel Snooze'
small.text-muted: em.
&nbsp; Snooze expires #[date-from-now(:value='req.snoozedUntil')]
</template>
<script>
'use static'
import { mapState } from 'vuex'
import actions from '@/store/action-types'
export default {
name: 'answered',
data () {
return {
requests: [],
loaded: false
}
},
computed: {
toast () {
return this.$parent.$refs.toast
},
...mapState(['journal', 'isLoadingJournal'])
},
methods: {
async ensureJournal () {
if (!Array.isArray(this.journal)) {
this.loaded = false
await this.$store.dispatch(actions.LOAD_JOURNAL, this.$Progress)
}
this.requests = this.journal
.filter(req => req.snoozedUntil > Date.now())
.sort((a, b) => a.snoozedUntil - b.snoozedUntil)
this.loaded = true
},
async cancelSnooze (requestId) {
await this.$store.dispatch(actions.SNOOZE_REQUEST, {
progress: this.$Progress,
requestId: requestId,
until: 0
})
this.toast.showToast('Request un-snoozed', { theme: 'success' })
this.ensureJournal()
}
},
async mounted () {
await this.ensureJournal()
}
}
</script>
<style>
.mpj-snoozed-list p {
border-top: solid 1px lightgray;
}
.mpj-snoozed-list p:first-child {
border-top: none;
}
</style>

View File

@@ -0,0 +1,15 @@
<template lang="pug">
i.material-icons(v-html='icon')
</template>
<script>
export default {
name: 'md-icon',
props: {
icon: {
type: String,
required: true
}
}
}
</script>

View File

@@ -0,0 +1,96 @@
<template lang="pug">
nav.mpj-top-nav.mpj-bg(role='menubar')
router-link.title(:to="{ name: 'Home' }"
role='menuitem')
span(style='font-weight:100;') my
span(style='font-weight:600;') Prayer
span(style='font-weight:700;') Journal
router-link(v-if='isAuthenticated'
:to="{ name: 'Journal' }"
role='menuitem') Journal
router-link(v-if='isAuthenticated'
:to="{ name: 'ActiveRequests' }"
role='menuitem') Active
router-link(v-if='hasSnoozed'
:to="{ name: 'SnoozedRequests' }"
role='menuitem') Snoozed
router-link(v-if='isAuthenticated'
:to="{ name: 'AnsweredRequests' }"
role='menuitem') Answered
a(v-if='isAuthenticated'
href='#'
role='menuitem'
@click.stop='logOff()') Log Off
a(v-if='!isAuthenticated'
href='#'
role='menuitem'
@click.stop='logOn()') Log On
a(href='https://bit-badger.github.io/myPrayerJournal/'
target='_blank'
role='menuitem'
@click.stop='') Docs
</template>
<script>
'use strict'
import { mapState } from 'vuex'
import AuthService from '@/auth/AuthService'
export default {
name: 'navigation',
data () {
return {
auth0: new AuthService()
}
},
computed: {
hasSnoozed () {
return this.isAuthenticated &&
Array.isArray(this.journal) &&
this.journal.filter(req => req.snoozedUntil > Date.now()).length > 0
},
...mapState([ 'journal', 'isAuthenticated' ])
},
methods: {
logOn () {
this.auth0.login()
},
logOff () {
this.auth0.logout(this.$store, this.$router)
}
}
}
</script>
<style>
.mpj-top-nav {
position: fixed;
display: flex;
flex-flow: row wrap;
align-items: center;
top: 0;
left: 0;
width: 100%;
padding-left: .5rem;
min-height: 50px;
}
.mpj-top-nav a:link,
.mpj-top-nav a:visited {
text-decoration: none;
color: rgba(255, 255, 255, .75);
padding-left: 1rem;
}
.mpj-top-nav a:link.router-link-active,
.mpj-top-nav a:visited.router-link-active,
.mpj-top-nav a:hover {
color: white;
}
.mpj-top-nav .title {
font-size: 1.25rem;
color: white;
padding-left: 1.25rem;
padding-right: 1.25rem;
}
</style>

View File

@@ -0,0 +1,59 @@
<template lang="pug">
article.mpj-main-content(role='main')
page-title(title='Active Requests')
div(v-if='loaded').mpj-request-list
p.mpj-text-center(v-if='requests.length === 0'): em.
No active requests found; return to #[router-link(:to='{ name: "Journal" } ') your journal]
request-list-item(v-for='req in requests'
:key='req.requestId'
:request='req'
:toast='toast')
p(v-else) Loading journal...
</template>
<script>
'use strict'
import { mapState } from 'vuex'
import RequestListItem from '@/components/request/RequestListItem'
import actions from '@/store/action-types'
export default {
name: 'active-requests',
components: {
RequestListItem
},
data () {
return {
requests: [],
loaded: false
}
},
computed: {
toast () {
return this.$parent.$refs.toast
},
...mapState(['journal', 'isLoadingJournal'])
},
created () {
this.$on('requestUnsnoozed', this.ensureJournal)
this.$on('requestNowShown', this.ensureJournal)
},
methods: {
async ensureJournal () {
if (!Array.isArray(this.journal)) {
this.loaded = false
await this.$store.dispatch(actions.LOAD_JOURNAL, this.$Progress)
}
this.requests = this.journal
.sort((a, b) => a.showAfter - b.showAfter)
this.loaded = true
}
},
async mounted () {
await this.ensureJournal()
}
}
</script>

View File

@@ -1,30 +1,28 @@
<template lang="pug">
article
article.mpj-main-content(role='main')
page-title(title='Answered Requests')
p(v-if='!loaded') Loading answered requests...
div(v-if='loaded').mpj-answered-list
div(v-if='loaded').mpj-request-list
p.text-center(v-if='requests.length === 0'): em.
No answered requests found; once you have marked one as &ldquo;Answered&rdquo;, it will appear here
p.mpj-request-text(v-for='req in requests' :key='req.requestId')
| {{ req.text }}
br
br
b-btn(:to='{ name: "AnsweredDetail", params: { id: req.requestId }}'
size='sm'
variant='outline-secondary')
icon(name='search')
= ' View Full Request'
small.text-muted: em.
&nbsp; Answered #[date-from-now(:value='req.asOf')]
request-list-item(v-for='req in requests'
:key='req.requestId'
:request='req'
:toast='toast')
p(v-else) Loading answered requests...
</template>
<script>
'use static'
'use strict'
import api from '@/api'
import RequestListItem from '@/components/request/RequestListItem'
export default {
name: 'answered',
name: 'answered-requests',
components: {
RequestListItem
},
data () {
return {
requests: [],
@@ -52,12 +50,3 @@ export default {
}
}
</script>
<style>
.mpj-answered-list p {
border-top: solid 1px lightgray;
}
.mpj-answered-list p:first-child {
border-top: none;
}
</style>

View File

@@ -1,89 +1,204 @@
<template lang="pug">
b-modal(v-model='editVisible'
header-bg-variant='mpj'
header-text-variant='light'
size='lg'
title='Edit Prayer Request'
@edit='openDialog()'
@shows='focusRequestText')
b-form
b-form-group(label='Prayer Request'
label-for='request_text')
b-textarea#request_text(ref='toFocus'
v-model='form.requestText'
:rows='10'
@blur='trimText()')
b-form-group(label='Also Mark As')
b-radio-group(v-model='form.status'
buttons)
b-radio(value='Updated') Updated
b-radio(value='Prayed') Prayed
b-radio(value='Answered') Answered
div.w-100.text-right(slot='modal-footer')
b-btn(variant='primary'
@click='saveRequest()') Save
article.mpj-main-content(role='main')
page-title(:title='title')
.mpj-narrow
label(for='request_text')
| Prayer Request
br
textarea(v-model='form.requestText'
:rows='10'
@blur='trimText()'
autofocus).mpj-full-width
br
template(v-if='!isNew')
label Also Mark As
br
label.normal
input(v-model='form.status'
type='radio'
name='status'
value='Updated')
| Updated
| &nbsp; &nbsp;
label.normal
input(v-model='form.status'
type='radio'
name='status'
value='Prayed')
| Prayed
| &nbsp; &nbsp;
label.normal
input(v-model='form.status'
type='radio'
name='status'
value='Answered')
| Answered
br
label Recurrence
| &nbsp; &nbsp;
b-btn(variant='outline-secondary'
@click='closeDialog()') Cancel
em.mpj-muted-text After prayer, request reappears...
br
label.normal
input(v-model='form.recur.typ'
type='radio'
name='recur'
value='immediate')
| Immediately
| &nbsp; &nbsp;
label.normal
input(v-model='form.recur.typ'
type='radio'
name='recur'
value='other')
| Every...
input(v-model='form.recur.count'
type='number'
:disabled='!showRecurrence').mpj-recur-count
select(v-model='form.recur.other'
:disabled='!showRecurrence').mpj-recur-type
option(value='hours') hours
option(value='days') days
option(value='weeks') weeks
.mpj-text-right
button(:disabled='!isValidRecurrence'
@click.stop='saveRequest()').primary.
#[md-icon(icon='save')] Save
| &nbsp; &nbsp;
button(@click.stop='goBack()').
#[md-icon(icon='arrow_back')] Cancel
</template>
<script>
'use strict'
import { mapState } from 'vuex'
import actions from '@/store/action-types'
export default {
name: 'edit-request',
props: {
toast: { required: true },
events: { required: true }
id: {
type: String,
required: true
}
},
data () {
return {
editVisible: false,
title: 'Edit Prayer Request',
isNew: false,
form: {
requestId: '',
requestText: '',
status: 'Updated'
status: 'Updated',
recur: {
typ: 'immediate',
other: '',
count: ''
}
}
}
},
created () {
this.events.$on('edit', this.openDialog)
computed: {
isValidRecurrence () {
if (this.form.recur.typ === 'immediate') return true
const count = Number.parseInt(this.form.recur.count)
if (isNaN(count) || this.form.recur.other === '') return false
if (this.form.recur.other === 'hours' && count > (365 * 24)) return false
if (this.form.recur.other === 'days' && count > 365) return false
if (this.form.recur.other === 'weeks' && count > 52) return false
return true
},
showRecurrence () {
return this.form.recur.typ !== 'immediate'
},
toast () {
return this.$parent.$refs.toast
},
...mapState(['journal'])
},
methods: {
closeDialog () {
async mounted () {
await this.ensureJournal()
if (this.id === 'new') {
this.title = 'Add Prayer Request'
this.isNew = true
this.form.requestId = ''
this.form.requestText = ''
this.form.status = 'Created'
this.form.recur.typ = 'immediate'
this.form.recur.other = ''
this.form.recur.count = ''
} else {
this.title = 'Edit Prayer Request'
this.isNew = false
if (this.journal.length === 0) {
await this.$store.dispatch(actions.LOAD_JOURNAL, this.$Progress)
}
const req = this.journal.filter(r => r.requestId === this.id)[0]
this.form.requestId = this.id
this.form.requestText = req.text
this.form.status = 'Updated'
this.editVisible = false
},
focusRequestText (e) {
this.$refs.toFocus.focus()
},
openDialog (request) {
this.form.requestId = request.requestId
this.form.requestText = request.text
this.editVisible = true
this.focusRequestText(null)
if (req.recurType === 'immediate') {
this.form.recur.typ = 'immediate'
this.form.recur.other = ''
this.form.recur.count = ''
} else {
this.form.recur.typ = 'other'
this.form.recur.other = req.recurType
this.form.recur.count = req.recurCount
}
}
},
methods: {
goBack () {
this.$router.go(-1)
},
trimText () {
this.form.requestText = this.form.requestText.trim()
},
async saveRequest () {
await this.$store.dispatch(actions.UPDATE_REQUEST, {
progress: this.$Progress,
requestId: this.form.requestId,
updateText: this.form.requestText,
status: this.form.status
})
if (this.form.status === 'Answered') {
this.toast.showToast('Request updated and removed from active journal', { theme: 'success' })
} else {
this.toast.showToast('Request updated', { theme: 'success' })
async ensureJournal () {
if (!Array.isArray(this.journal)) {
await this.$store.dispatch(actions.LOAD_JOURNAL, this.$Progress)
}
this.closeDialog()
},
async saveRequest () {
if (this.isNew) {
await this.$store.dispatch(actions.ADD_REQUEST, {
progress: this.$Progress,
requestText: this.form.requestText,
recurType: this.form.recur.typ === 'immediate' ? 'immediate' : this.form.recur.other,
recurCount: this.form.recur.typ === 'immediate' ? 0 : Number.parseInt(this.form.recur.count)
})
this.toast.showToast('New prayer request added', { theme: 'success' })
} else {
await this.$store.dispatch(actions.UPDATE_REQUEST, {
progress: this.$Progress,
requestId: this.form.requestId,
updateText: this.form.requestText,
status: this.form.status,
recurType: this.form.recur.typ === 'immediate' ? 'immediate' : this.form.recur.other,
recurCount: this.form.recur.typ === 'immediate' ? 0 : Number.parseInt(this.form.recur.count)
})
if (this.form.status === 'Answered') {
this.toast.showToast('Request updated and removed from active journal', { theme: 'success' })
} else {
this.toast.showToast('Request updated', { theme: 'success' })
}
}
this.goBack()
}
}
}
</script>
<style>
.mpj-recur-count {
width: 3rem;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.mpj-recur-type {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
</style>

View File

@@ -1,56 +1,90 @@
<template lang="pug">
span
b-modal(v-model='historyVisible'
header-bg-variant='mpj'
header-text-variant='light'
size='lg'
title='Prayer Request History'
@shows='focusRequestText')
b-list-group(v-if='null !== full'
flush)
full-request-history(v-for='item in full.history'
:key='item.asOf'
:history='item')
div.w-100.text-right(slot='modal-footer')
b-btn(variant='primary'
@click='closeDialog()') Close
article.mpj-main-content(role='main')
page-title(title='Full Prayer Request')
template(v-if='request')
p
span(v-if='isAnswered') Answered {{ formatDate(answered) }} (#[date-from-now(:value='answered')]) &nbsp;
small: em.mpj-muted-text prayed {{ prayedCount }} times, open {{ openDays }} days
p.mpj-request-text {{ lastText }}
br
table.mpj-request-log
thead
tr
th Action
th Update / Notes
tbody
tr(v-for='item in log' :key='item.asOf')
td {{ item.status }} on #[span.mpj-text-nowrap {{ formatDate(item.asOf) }}]
td(v-if='item.text').mpj-request-text {{ item.text.fields[0] }}
td(v-else) &nbsp;
p(v-else) Loading request...
</template>
<script>
'use strict'
import FullRequestHistory from './FullRequestHistory'
import moment from 'moment'
import api from '@/api'
const asOfDesc = (a, b) => b.asOf - a.asOf
export default {
name: 'full-request',
components: {
FullRequestHistory
},
props: {
events: { required: true }
id: {
type: String,
required: true
}
},
data () {
return {
historyVisible: false,
full: null
request: null
}
},
created () {
this.events.$on('full', this.openDialog)
computed: {
answered () {
return this.request.history.find(hist => hist.status === 'Answered').asOf
},
isAnswered () {
return this.request.history.filter(hist => hist.status === 'Answered').length > 0
},
lastText () {
return this.request.history
.filter(hist => hist.text)
.sort(asOfDesc)[0].text.fields[0]
},
log () {
const allHistory = (this.request.notes || [])
.map(note => ({ asOf: note.asOf, text: { case: 'Some', fields: [ note.notes ] }, status: 'Notes' }))
.concat(this.request.history)
.sort(asOfDesc)
// Skip the first entry for answered requests; that info is already displayed
return this.isAnswered ? allHistory.slice(1) : allHistory
},
openDays () {
const asOf = this.isAnswered ? this.answered : Date.now()
return Math.floor(
(asOf - this.request.history.find(hist => hist.status === 'Created').asOf) / 1000 / 60 / 60 / 24)
},
prayedCount () {
return this.request.history.filter(hist => hist.status === 'Prayed').length
}
},
async mounted () {
this.$Progress.start()
try {
const req = await api.getFullRequest(this.id)
this.request = req.data
this.$Progress.finish()
} catch (e) {
console.log(e)
this.$Progress.fail()
}
},
methods: {
closeDialog () {
this.full = null
this.historyVisible = false
},
async openDialog (requestId) {
this.historyVisible = true
this.$Progress.start()
const req = await api.getFullRequest(requestId)
this.full = req.data
this.$Progress.finish()
formatDate (asOf) {
return moment(asOf).format('LL')
}
}
}

View File

@@ -1,28 +0,0 @@
<template lang="pug">
b-list-group-item
| {{ history.status }}
|
small.text-muted(:title='actualDate') {{ asOf }}
div(v-if='history.text').mpj-request-text {{ history.text.fields[0] }}
</template>
<script>
'use strict'
import moment from 'moment'
export default {
name: 'full-request-history',
props: {
history: { required: true }
},
computed: {
asOf () {
return moment(this.history.asOf).fromNow()
},
actualDate () {
return moment(this.history.asOf).format('LLLL')
}
}
}
</script>

View File

@@ -1,71 +0,0 @@
<template lang="pug">
div
b-btn(@click='openDialog()' size='sm' variant='primary')
icon(name='plus')
| &nbsp; Add a New Request
b-modal(v-model='showNewVisible'
header-bg-variant='mpj'
header-text-variant='light'
size='lg'
title='Add a New Prayer Request'
@shown='focusRequestText')
b-form
b-form-group(label='Prayer Request'
label-for='request_text')
b-textarea#request_text(ref='toFocus'
v-model='form.requestText'
:rows='10'
@blur='trimText()')
div.w-100.text-right(slot='modal-footer')
b-btn(variant='primary'
@click='saveRequest()') Save
| &nbsp; &nbsp;
b-btn(variant='outline-secondary'
@click='closeDialog()') Cancel
toast(ref='toast')
</template>
<script>
'use strict'
import actions from '@/store/action-types'
export default {
name: 'new-request',
data () {
return {
showNewVisible: false,
form: {
requestText: ''
},
formLabelWidth: '120px'
}
},
mounted () {
this.$refs.toast.setOptions({ position: 'bottom right' })
},
methods: {
closeDialog () {
this.form.requestText = ''
this.showNewVisible = false
},
focusRequestText (e) {
this.$refs.toFocus.focus()
},
openDialog () {
this.showNewVisible = true
},
trimText () {
this.form.requestText = this.form.requestText.trim()
},
async saveRequest () {
await this.$store.dispatch(actions.ADD_REQUEST, {
progress: this.$Progress,
requestText: this.form.requestText
})
this.$refs.toast.showToast('New prayer request added', { theme: 'success' })
this.closeDialog()
}
}
}
</script>

View File

@@ -1,36 +1,33 @@
<template lang="pug">
b-modal(v-model='notesVisible'
header-bg-variant='mpj'
header-text-variant='light'
size='lg'
title='Add Notes to Prayer Request'
@edit='openDialog()'
@shows='focusNotes')
b-form
b-form-group(label='Notes'
label-for='notes')
b-textarea#notes(ref='toFocus'
v-model='form.notes'
:rows='10'
@blur='trimText()')
div(v-if='hasPriorNotes')
p.text-center: strong Prior Notes for This Request
b-list-group(flush)
b-list-group-item(v-for='note in priorNotes'
:key='note.asOf')
small.text-muted: date-from-now(:value='note.asOf')
br
div.mpj-request-text {{ note.notes }}
div(v-else-if='noPriorNotes').text-center.text-muted There are no prior notes for this request
div(v-else).text-center
b-btn(variant='outline-secondary'
@click='loadNotes()') Load Prior Notes
div.w-100.text-right(slot='modal-footer')
b-btn(variant='primary'
@click='saveNotes()') Save
| &nbsp; &nbsp;
b-btn(variant='outline-secondary'
@click='closeDialog()') Cancel
.mpj-modal(v-show='notesVisible')
.mpj-modal-content.mpj-narrow
header.mpj-bg
h5 Add Notes to Prayer Request
label
| Notes
br
textarea(v-model='form.notes'
:rows='10'
@blur='trimText()').mpj-full-width
.mpj-text-right
button(@click='saveNotes()').primary.
#[md-icon(icon='save')] Save
| &nbsp; &nbsp;
button(@click='closeDialog()').
#[md-icon(icon='undo')] Cancel
hr
div(v-if='hasPriorNotes')
p.mpj-text-center: strong Prior Notes for This Request
.mpj-note-list
p(v-for='note in priorNotes'
:key='note.asOf')
small.mpj-muted-text: date-from-now(:value='note.asOf')
br
span.mpj-request-text {{ note.notes }}
div(v-else-if='noPriorNotes').mpj-text-center.mpj-muted-text There are no prior notes for this request
div(v-else).mpj-text-center
button(@click='loadNotes()').
#[md-icon(icon='cloud_download')] Load Prior Notes
</template>
<script>
@@ -74,15 +71,11 @@ export default {
this.priorNotesLoaded = false
this.notesVisible = false
},
focusNotes (e) {
this.$refs.toFocus.focus()
},
async loadNotes () {
this.$Progress.start()
try {
const notes = await api.getNotes(this.form.requestId)
this.priorNotes = notes.data
console.log(this.priorNotes)
this.$Progress.finish()
} catch (e) {
console.error(e)
@@ -94,7 +87,6 @@ export default {
openDialog (request) {
this.form.requestId = request.requestId
this.notesVisible = true
this.focusNotes(null)
},
async saveNotes () {
this.$Progress.start()
@@ -114,3 +106,9 @@ export default {
}
}
</script>
<style>
.mpj-note-list p {
border-top: dotted 1px lightgray;
}
</style>

View File

@@ -1,19 +1,17 @@
<template lang="pug">
b-col(v-if="!isSnoozed" md='6' lg='4')
.mpj-request-card
b-card-header.text-center.py-1.
#[b-btn(@click='markPrayed()' variant='outline-primary' title='Pray' size='sm'): icon(name='check')]
#[b-btn(@click.stop='showEdit()' variant='outline-secondary' title='Edit' size='sm'): icon(name='pencil')]
#[b-btn(@click.stop='showNotes()' variant='outline-secondary' title='Add Notes' size='sm'): icon(name='file-text-o')]
#[b-btn(@click.stop='showFull()' variant='outline-secondary' title='View Full Request' size='sm'): icon(name='search')]
#[b-btn(@click.stop='snooze()' variant='outline-secondary' title='Snooze Request' size='sm'): icon(name='clock-o')]
b-card-body.p-0
p.card-text.mpj-request-text.mb-1.px-3.pt-3
| {{ request.text }}
p.card-text.p-0.pr-1.text-right: small.text-muted: em
= '(last activity '
date-from-now(:value='request.asOf')
| )
.mpj-request-card(v-if='shouldDisplay')
header.mpj-card-header(role='toolbar').
#[button(@click='markPrayed()' title='Pray').primary: md-icon(icon='done')]
#[button(@click.stop='showEdit()' title='Edit'): md-icon(icon='edit')]
#[button(@click.stop='showNotes()' title='Add Notes'): md-icon(icon='comment')]
#[button(@click.stop='snooze()' title='Snooze Request'): md-icon(icon='schedule')]
div
p.card-text.mpj-request-text
| {{ request.text }}
p.as-of.mpj-text-right: small.mpj-muted-text: em
= '(last activity '
date-from-now(:value='request.asOf')
| )
</template>
<script>
@@ -29,8 +27,9 @@ export default {
events: { required: true }
},
computed: {
isSnoozed () {
return Date.now() < this.request.snoozedUntil
shouldDisplay () {
const now = Date.now()
return Math.max(now, this.request.showAfter, this.request.snoozedUntil) === now
}
},
methods: {
@@ -44,10 +43,7 @@ export default {
this.toast.showToast('Request marked as prayed', { theme: 'success' })
},
showEdit () {
this.events.$emit('edit', this.request)
},
showFull () {
this.events.$emit('full', this.request.requestId)
this.$router.push({ name: 'EditRequest', params: { id: this.request.requestId } })
},
showNotes () {
this.events.$emit('notes', this.request)
@@ -63,6 +59,35 @@ export default {
.mpj-request-card {
border: solid 1px darkgray;
border-radius: 5px;
margin-bottom: 15px;
width: 20rem;
margin: .5rem;
}
@media screen and (max-width: 20rem) {
.mpj-request-card {
width: 100%;
}
}
.mpj-card-header {
display: flex;
flex-flow: row;
justify-content: center;
background-image: -webkit-gradient(linear, left top, left bottom, from(lightgray), to(whitesmoke));
background-image: -webkit-linear-gradient(top, lightgray, whitesmoke);
background-image: -moz-linear-gradient(top, lightgray, whitesmoke);
background-image: linear-gradient(to bottom, lightgray, whitesmoke);
}
.mpj-card-header button {
margin: .25rem;
padding: 0 .25rem;
}
.mpj-card-header button .material-icons {
font-size: 1.3rem;
}
.mpj-request-card .card-text {
margin-left: 1rem;
margin-right: 1rem;
}
.mpj-request-card .as-of {
margin-right: .25rem;
}
</style>

View File

@@ -0,0 +1,86 @@
<template lang="pug">
p.mpj-request-text
| {{ request.text }}
br
br
button(@click='viewFull'
title='View Full Request').
#[md-icon(icon='description')] View Full Request
| &nbsp; &nbsp;
template(v-if='!isAnswered')
button(@click='editRequest'
title='Edit Request').
#[md-icon(icon='edit')] Edit Request
| &nbsp; &nbsp;
template(v-if='isSnoozed')
button(@click='cancelSnooze()').
#[md-icon(icon='restore')] Cancel Snooze
| &nbsp; &nbsp;
template(v-if='isPending')
button(@click='showNow()').
#[md-icon(icon='restore')] Show Now
br(v-if='isSnoozed || isPending || isAnswered')
small(v-if='isSnoozed').mpj-muted-text: em.
&nbsp; Snooze expires #[date-from-now(:value='request.snoozedUntil')]
small(v-if='isPending').mpj-muted-text: em.
&nbsp; Request scheduled to reappear #[date-from-now(:value='request.showAfter')]
small(v-if='isAnswered').mpj-muted-text: em.
&nbsp; Answered #[date-from-now(:value='request.asOf')]
</template>
<script>
'use strict'
import actions from '@/store/action-types'
export default {
name: 'request-list-item',
props: {
request: { required: true },
toast: { required: true }
},
data () {
return {}
},
computed: {
answered () {
return this.request.history.find(hist => hist.status === 'Answered').asOf
},
isAnswered () {
return this.request.lastStatus === 'Answered'
},
isPending () {
return !this.isSnoozed && this.request.showAfter > Date.now()
},
isSnoozed () {
return this.request.snoozedUntil > Date.now()
}
},
methods: {
async cancelSnooze () {
await this.$store.dispatch(actions.SNOOZE_REQUEST, {
progress: this.$Progress,
requestId: this.request.requestId,
until: 0
})
this.toast.showToast('Request un-snoozed', { theme: 'success' })
this.$parent.$emit('requestUnsnoozed')
},
editRequest () {
this.$router.push({ name: 'EditRequest', params: { id: this.request.requestId } })
},
async showNow () {
await this.$store.dispatch(actions.SHOW_REQUEST_NOW, {
progress: this.$Progress,
requestId: this.request.requestId,
showAfter: Date.now()
})
this.toast.showToast('Recurrence skipped; request now shows in journal', { theme: 'success' })
this.$parent.$emit('requestNowShown')
},
viewFull () {
this.$router.push({ name: 'FullRequest', params: { id: this.request.requestId } })
}
}
}
</script>

View File

@@ -1,23 +1,22 @@
<template lang="pug">
b-modal(v-model='snoozeVisible'
header-bg-variant='mpj'
header-text-variant='light'
size='lg'
title='Snooze Prayer Request'
@edit='openDialog()')
b-form
b-form-group(label='Until'
label-for='until')
b-input#until(type='date'
v-model='form.snoozedUntil'
autofocus)
div.w-100.text-right(slot='modal-footer')
b-btn(variant='primary'
:disabled='!isValid'
@click='snoozeRequest()') Snooze
| &nbsp; &nbsp;
b-btn(variant='outline-secondary'
@click='closeDialog()') Cancel
.mpj-modal(v-show='snoozeVisible')
.mpj-modal-content.mpj-skinny
header.mpj-bg
h5 Snooze Prayer Request
p.mpj-text-center
label
= 'Until '
input(v-model='form.snoozedUntil'
type='date'
autofocus)
br
.mpj-text-right
button.primary(:disabled='!isValid'
@click='snoozeRequest()').
#[md-icon(icon='snooze')] Snooze
| &nbsp; &nbsp;
button(@click='closeDialog()').
#[md-icon(icon='undo')] Cancel
</template>
<script>

View File

@@ -0,0 +1,59 @@
<template lang="pug">
article.mpj-main-content(role='main')
page-title(title='Snoozed Requests')
div(v-if='loaded').mpj-request-list
p.mpj-text-center(v-if='requests.length === 0'): em.
No snoozed requests found; return to #[router-link(:to='{ name: "Journal" } ') your journal]
request-list-item(v-for='req in requests'
:key='req.requestId'
:request='req'
:toast='toast')
p(v-else) Loading journal...
</template>
<script>
'use strict'
import { mapState } from 'vuex'
import actions from '@/store/action-types'
import RequestListItem from '@/components/request/RequestListItem'
export default {
name: 'snoozed-requests',
components: {
RequestListItem
},
data () {
return {
requests: [],
loaded: false
}
},
computed: {
toast () {
return this.$parent.$refs.toast
},
...mapState(['journal', 'isLoadingJournal'])
},
created () {
this.$on('requestUnsnoozed', this.ensureJournal)
},
methods: {
async ensureJournal () {
if (!Array.isArray(this.journal)) {
this.loaded = false
await this.$store.dispatch(actions.LOAD_JOURNAL, this.$Progress)
}
this.requests = this.journal
.filter(req => req.snoozedUntil > Date.now())
.sort((a, b) => a.snoozedUntil - b.snoozedUntil)
this.loaded = true
}
},
async mounted () {
await this.ensureJournal()
}
}
</script>

View File

@@ -1,5 +1,5 @@
<template lang="pug">
article
article.mpj-main-content(role='main')
pageTitle(title='Logging On')
p Logging you on...
</template>

View File

@@ -1,31 +1,18 @@
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
import Icon from 'vue-awesome/components/Icon'
import VueProgressBar from 'vue-progressbar'
import VueToast from 'vue-toast'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import 'bootstrap/dist/css/bootstrap.css'
import 'vue-toast/dist/vue-toast.min.css'
// Only import the icons we need; the whole set is ~500K!
import 'vue-awesome/icons/check'
import 'vue-awesome/icons/clock-o'
import 'vue-awesome/icons/file-text-o'
import 'vue-awesome/icons/pencil'
import 'vue-awesome/icons/plus'
import 'vue-awesome/icons/search'
import 'vue-awesome/icons/times'
import App from './App.vue'
import App from './App'
import router from './router'
import store from './store'
import DateFromNow from './components/common/DateFromNow'
import MaterialDesignIcon from './components/common/MaterialDesignIcon'
import PageTitle from './components/common/PageTitle'
Vue.config.productionTip = false
Vue.use(BootstrapVue)
Vue.use(VueProgressBar, {
color: 'yellow',
failedColor: 'red',
@@ -37,8 +24,8 @@ Vue.use(VueProgressBar, {
}
})
Vue.component('icon', Icon)
Vue.component('date-from-now', DateFromNow)
Vue.component('md-icon', MaterialDesignIcon)
Vue.component('page-title', PageTitle)
Vue.component('toast', VueToast)

View File

@@ -1,13 +1,17 @@
'use strict'
import Vue from 'vue'
import Router from 'vue-router'
import Answered from '@/components/Answered'
import AnsweredDetail from '@/components/AnsweredDetail'
import ActiveRequests from '@/components/request/ActiveRequests'
import AnsweredRequests from '@/components/request/AnsweredRequests'
import EditRequest from '@/components/request/EditRequest'
import FullRequest from '@/components/request/FullRequest'
import Home from '@/components/Home'
import Journal from '@/components/Journal'
import LogOn from '@/components/user/LogOn'
import PrivacyPolicy from '@/components/legal/PrivacyPolicy'
import Snoozed from '@/components/Snoozed'
import SnoozedRequests from '@/components/request/SnoozedRequests'
import TermsOfService from '@/components/legal/TermsOfService'
Vue.use(Router)
@@ -15,23 +19,19 @@ Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
},
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/answered/:id',
name: 'AnsweredDetail',
component: AnsweredDetail,
props: true
},
{
path: '/answered',
name: 'Answered',
component: Answered
},
{
path: '/journal',
name: 'Journal',
@@ -48,9 +48,31 @@ export default new Router({
component: TermsOfService
},
{
path: '/snoozed',
name: 'Snoozed',
component: Snoozed
path: '/request/:id/edit',
name: 'EditRequest',
component: EditRequest,
props: true
},
{
path: '/request/:id/full',
name: 'FullRequest',
component: FullRequest,
props: true
},
{
path: '/requests/active',
name: 'ActiveRequests',
component: ActiveRequests
},
{
path: '/requests/answered',
name: 'AnsweredRequests',
component: AnsweredRequests
},
{
path: '/requests/snoozed',
name: 'SnoozedRequests',
component: SnoozedRequests
},
{
path: '/user/log-on',

View File

@@ -7,6 +7,8 @@ export default {
LOAD_JOURNAL: 'load-journal',
/** Action to update a request */
UPDATE_REQUEST: 'update-request',
/** Action to skip the remaining recurrence period */
SHOW_REQUEST_NOW: 'show-request-now',
/** Action to snooze a request */
SNOOZE_REQUEST: 'snooze-request'
}

View File

@@ -1,3 +1,5 @@
'use strict'
import Vue from 'vue'
import Vuex from 'vuex'
@@ -71,10 +73,10 @@ export default new Vuex.Store({
}
},
actions: {
async [actions.ADD_REQUEST] ({ commit }, { progress, requestText }) {
async [actions.ADD_REQUEST] ({ commit }, { progress, requestText, recurType, recurCount }) {
progress.start()
try {
const newRequest = await api.addRequest(requestText)
const newRequest = await api.addRequest(requestText, recurType, recurCount)
commit(mutations.REQUEST_ADDED, newRequest.data)
progress.finish()
} catch (err) {
@@ -98,10 +100,28 @@ export default new Vuex.Store({
commit(mutations.LOADING_JOURNAL, false)
}
},
async [actions.UPDATE_REQUEST] ({ commit }, { progress, requestId, status, updateText }) {
async [actions.UPDATE_REQUEST] ({ commit, state }, { progress, requestId, status, updateText, recurType, recurCount }) {
progress.start()
try {
await api.updateRequest({ requestId, status, updateText })
let oldReq = (state.journal.filter(req => req.requestId === requestId) || [])[0] || {}
if (status !== 'Updated' || oldReq.text !== updateText) {
await api.updateRequest(requestId, status, updateText)
}
if (status === 'Updated' && (oldReq.recurType !== recurType || oldReq.recurCount !== recurCount)) {
await api.updateRecurrence(requestId, recurType, recurCount)
}
const request = await api.getRequest(requestId)
commit(mutations.REQUEST_UPDATED, request.data)
progress.finish()
} catch (err) {
logError(err)
progress.fail()
}
},
async [actions.SHOW_REQUEST_NOW] ({ commit }, { progress, requestId, showAfter }) {
progress.start()
try {
await api.showRequest(requestId, showAfter)
const request = await api.getRequest(requestId)
commit(mutations.REQUEST_UPDATED, request.data)
progress.finish()

View File

@@ -663,9 +663,9 @@
dependencies:
"@types/babel-types" "*"
"@vue/babel-preset-app@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-3.0.0.tgz#56bbd624eb78fa1d5f7bf992ac62e6fc7557bd71"
"@vue/babel-preset-app@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-3.0.1.tgz#24188938e93f259f7141a6a1190da9c511d123d8"
dependencies:
"@babel/plugin-proposal-class-properties" "7.0.0-beta.47"
"@babel/plugin-proposal-decorators" "7.0.0-beta.47"
@@ -679,34 +679,34 @@
babel-plugin-transform-vue-jsx "^4.0.1"
"@vue/cli-overlay@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@vue/cli-overlay/-/cli-overlay-3.0.0.tgz#580340afde2cf155b71c67907f27a69b906416b6"
version "3.0.1"
resolved "https://registry.yarnpkg.com/@vue/cli-overlay/-/cli-overlay-3.0.1.tgz#474067e18fc7c1b303c97901175d6441bfdcde6f"
"@vue/cli-plugin-babel@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-3.0.0.tgz#5e3e2ae3435929b26994250692fa7a20e5cd6883"
version "3.0.1"
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-3.0.1.tgz#a1691caf610d42800314ceb9e727a7668bfa3e7f"
dependencies:
"@babel/core" "7.0.0-beta.47"
"@vue/babel-preset-app" "^3.0.0"
"@vue/babel-preset-app" "^3.0.1"
babel-loader "^8.0.0-0"
"@vue/cli-plugin-eslint@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-3.0.0.tgz#97b163df1272b39e61e18c8add5dfe28a92375c4"
version "3.0.1"
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-3.0.1.tgz#9330cfe4843058f28b0ab2871a8862fa31f3a5c0"
dependencies:
"@vue/cli-shared-utils" "^3.0.0"
"@vue/cli-shared-utils" "^3.0.1"
babel-eslint "^8.2.5"
eslint "^4.19.1"
eslint-loader "^2.0.0"
eslint-plugin-vue "^4.5.0"
"@vue/cli-service@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-3.0.0.tgz#c808a846072dcf5751aad786439f53fc0a812bf4"
version "3.0.1"
resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-3.0.1.tgz#086c4b3b78bda7b0f4e1e7324237c62c89c5e6b3"
dependencies:
"@intervolga/optimize-cssnano-plugin" "^1.0.5"
"@vue/cli-overlay" "^3.0.0"
"@vue/cli-shared-utils" "^3.0.0"
"@vue/cli-shared-utils" "^3.0.1"
"@vue/preload-webpack-plugin" "^1.1.0"
"@vue/web-component-wrapper" "^1.2.0"
acorn "^5.7.1"
@@ -719,6 +719,7 @@
cliui "^4.1.0"
copy-webpack-plugin "^4.5.2"
css-loader "^1.0.0"
cssnano "^4.0.0"
debug "^3.1.0"
escape-string-regexp "^1.0.5"
file-loader "^1.1.11"
@@ -744,7 +745,7 @@
string.prototype.padend "^3.0.0"
thread-loader "^1.1.5"
uglifyjs-webpack-plugin "^1.2.7"
url-loader "^1.0.1"
url-loader "^1.1.0"
vue-loader "^15.3.0"
webpack "^4.15.1"
webpack-bundle-analyzer "^2.13.1"
@@ -753,9 +754,9 @@
webpack-merge "^4.1.3"
yorkie "^2.0.0"
"@vue/cli-shared-utils@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-3.0.0.tgz#f4886ce9a62dd2088e112af4d54f61c1667318d0"
"@vue/cli-shared-utils@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-3.0.1.tgz#1084f8e4c20a01b3bb17059992aedc6d4774e270"
dependencies:
chalk "^2.4.1"
execa "^0.10.0"
@@ -770,8 +771,8 @@
string.prototype.padstart "^3.0.0"
"@vue/component-compiler-utils@^2.0.0":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.1.2.tgz#75e7cc8496baecbb0994dc8783571d9ff07737fe"
version "2.2.0"
resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.2.0.tgz#bbbb7ed38a9a8a7c93abe7ef2e54a90a04b631b4"
dependencies:
consolidate "^0.15.1"
hash-sum "^1.0.2"
@@ -784,8 +785,8 @@
vue-template-es2015-compiler "^1.6.0"
"@vue/eslint-config-standard@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@vue/eslint-config-standard/-/eslint-config-standard-3.0.0.tgz#2a995fa4fac38bbb0947c8bceb4144eabf62df6a"
version "3.0.1"
resolved "https://registry.yarnpkg.com/@vue/eslint-config-standard/-/eslint-config-standard-3.0.1.tgz#34f322c857ee525aa6d0edda83b2b1698278b682"
dependencies:
eslint-config-standard "^12.0.0-alpha.0"
eslint-plugin-import "^2.11.0"
@@ -993,6 +994,10 @@ address@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9"
ajv-errors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59"
ajv-keywords@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
@@ -1011,13 +1016,13 @@ ajv@^5.2.3, ajv@^5.3.0:
json-schema-traverse "^0.3.0"
ajv@^6.1.0:
version "6.5.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.2.tgz#678495f9b82f7cca6be248dd92f59bff5e1f4360"
version "6.5.3"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.3.tgz#71a569d189ecf4f4f321224fecb166f071dd90f9"
dependencies:
fast-deep-equal "^2.0.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.1"
uri-js "^4.2.2"
align-text@^0.1.1, align-text@^0.1.3:
version "0.1.4"
@@ -1031,10 +1036,6 @@ alphanum-sort@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
ansi-escapes@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
ansi-escapes@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30"
@@ -1215,8 +1216,8 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
atob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.1.tgz#ae2d5a729477f289d60dd7f96a6314a22dd6c22a"
version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
auth0-js@^9.7.3:
version "9.7.3"
@@ -1305,15 +1306,7 @@ babel-plugin-transform-vue-jsx@^4.0.1:
dependencies:
esutils "^2.0.2"
babel-polyfill@6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d"
dependencies:
babel-runtime "^6.22.0"
core-js "^2.4.0"
regenerator-runtime "^0.10.0"
babel-runtime@^6.22.0, babel-runtime@^6.26.0:
babel-runtime@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
dependencies:
@@ -1425,21 +1418,6 @@ boolbase@^1.0.0, boolbase@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
bootstrap-vue@^2.0.0-rc.11:
version "2.0.0-rc.11"
resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.11.tgz#47aaa6d2a8d390477de75e636d8ea652b1d03f59"
dependencies:
bootstrap "^4.1.1"
lodash.get "^4.4.2"
lodash.startcase "^4.4.0"
opencollective "^1.0.3"
popper.js "^1.12.9"
vue-functional-data-merge "^2.0.5"
bootstrap@^4.1.1, bootstrap@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.3.tgz#0eb371af2c8448e8c210411d0cb824a6409a12be"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -1665,8 +1643,8 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000864, caniuse-lite@^1.0.30000876:
version "1.0.30000876"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000876.tgz#69fc1b696a35fd91089061aa916f677ee7057ada"
version "1.0.30000877"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000877.tgz#f189673b86ecc06436520e3e391de6a13ca923b4"
case-sensitive-paths-webpack-plugin@^2.1.2:
version "2.1.2"
@@ -1683,7 +1661,7 @@ center-align@^0.1.1:
align-text "^0.1.3"
lazy-cache "^1.0.3"
chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.3:
chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
dependencies:
@@ -1744,9 +1722,9 @@ chrome-trace-event@^1.0.0:
dependencies:
tslib "^1.9.0"
ci-info@^1.0.0:
version "1.1.3"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2"
ci-info@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.3.1.tgz#da21bc65a5f0d0d250c19a169065532b42fa048c"
cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
version "1.0.4"
@@ -2340,11 +2318,10 @@ defaults@^1.0.3:
clone "^1.0.2"
define-properties@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
dependencies:
foreach "^2.0.5"
object-keys "^1.0.8"
object-keys "^1.0.12"
define-property@^0.2.5:
version "0.2.5"
@@ -2555,8 +2532,8 @@ ejs@^2.5.7:
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
electron-to-chromium@^1.3.47, electron-to-chromium@^1.3.57:
version "1.3.57"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.57.tgz#61b2446f16af26fb8873210007a7637ad644c82d"
version "1.3.58"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.58.tgz#8267a4000014e93986d9d18c65a8b4022ca75188"
elliptic@^6.0.0:
version "6.4.1"
@@ -2578,12 +2555,6 @@ encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
encoding@^0.1.11:
version "0.1.12"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
dependencies:
iconv-lite "~0.4.13"
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
@@ -2975,7 +2946,7 @@ extend@^3.0.0, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
external-editor@^2.0.1, external-editor@^2.0.4:
external-editor@^2.0.4:
version "2.2.0"
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5"
dependencies:
@@ -3161,13 +3132,7 @@ flush-write-stream@^1.0.0:
inherits "^2.0.1"
readable-stream "^2.0.4"
follow-redirects@^1.0.0:
version "1.5.4"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.4.tgz#86d1bb946f24cb988d660aaa2ca2478c0772ead1"
dependencies:
debug "^3.1.0"
follow-redirects@^1.3.0:
follow-redirects@^1.0.0, follow-redirects@^1.3.0:
version "1.5.5"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.5.tgz#3c143ca599a2e22e62876687d68b23d55bad788b"
dependencies:
@@ -3183,10 +3148,6 @@ for-own@^0.1.4:
dependencies:
for-in "^1.0.1"
foreach@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@@ -3628,7 +3589,7 @@ iconv-lite@0.4.19:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
iconv-lite@^0.4.17, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
iconv-lite@^0.4.17, iconv-lite@^0.4.4:
version "0.4.23"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
dependencies:
@@ -3728,24 +3689,6 @@ ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
inquirer@3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.0.6.tgz#e04aaa9d05b7a3cb9b0f407d04375f0447190347"
dependencies:
ansi-escapes "^1.1.0"
chalk "^1.0.0"
cli-cursor "^2.1.0"
cli-width "^2.0.0"
external-editor "^2.0.1"
figures "^2.0.0"
lodash "^4.3.0"
mute-stream "0.0.7"
run-async "^2.2.0"
rx "^4.1.0"
string-width "^2.0.0"
strip-ansi "^3.0.0"
through "^2.3.6"
inquirer@^3.0.6:
version "3.3.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9"
@@ -3834,10 +3777,10 @@ is-callable@^1.1.1, is-callable@^1.1.3:
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
is-ci@^1.0.10:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5"
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.0.tgz#3f4a08d6303a09882cef3f0fb97439c5f5ce2d53"
dependencies:
ci-info "^1.0.0"
ci-info "^1.3.0"
is-color-stop@^1.0.0:
version "1.1.0"
@@ -4019,7 +3962,7 @@ is-resolvable@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
is-stream@^1.0.1, is-stream@^1.1.0:
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
@@ -4323,10 +4266,6 @@ lodash.defaultsdeep@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.0.tgz#bec1024f85b1bd96cbea405b23c14ad6443a6f81"
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
lodash.mapvalues@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
@@ -4335,10 +4274,6 @@ lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
lodash.startcase@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.startcase/-/lodash.startcase-4.4.0.tgz#9436e34ed26093ed7ffae1936144350915d9add8"
lodash.transform@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.transform/-/lodash.transform-4.6.0.tgz#12306422f63324aed8483d3f38332b5f670547a0"
@@ -4588,7 +4523,7 @@ minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
minimist@1.2.0, minimist@^1.1.3, minimist@^1.2.0:
minimist@^1.1.3, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
@@ -4633,7 +4568,7 @@ mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@
dependencies:
minimist "0.0.8"
moment@^2.22.2:
moment@^2.18.1:
version "2.22.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
@@ -4704,8 +4639,8 @@ negotiator@0.6.1:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
neo-async@^2.5.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.1.tgz#acb909e327b1e87ec9ef15f41b8a269512ad41ee"
version "2.5.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.2.tgz#489105ce7bc54e709d736b195f82135048c50fcc"
next-tick@1:
version "1.0.0"
@@ -4721,13 +4656,6 @@ no-case@^2.2.0:
dependencies:
lower-case "^1.1.1"
node-fetch@1.6.3:
version "1.6.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04"
dependencies:
encoding "^0.1.11"
is-stream "^1.0.1"
node-forge@0.7.5:
version "0.7.5"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df"
@@ -4883,7 +4811,7 @@ object-hash@^1.1.4:
version "1.3.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.0.tgz#76d9ba6ff113cf8efc0d996102851fe6723963e2"
object-keys@^1.0.11, object-keys@^1.0.8:
object-keys@^1.0.11, object-keys@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2"
@@ -4957,28 +4885,10 @@ onetime@^2.0.0:
dependencies:
mimic-fn "^1.0.0"
opencollective@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/opencollective/-/opencollective-1.0.3.tgz#aee6372bc28144583690c3ca8daecfc120dd0ef1"
dependencies:
babel-polyfill "6.23.0"
chalk "1.1.3"
inquirer "3.0.6"
minimist "1.2.0"
node-fetch "1.6.3"
opn "4.0.2"
opener@^1.4.3:
version "1.5.0"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.0.tgz#24222fb4ad423ba21f5bf38855cebe44220f6531"
opn@4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/opn/-/opn-4.0.2.tgz#7abc22e644dff63b0a96d5ab7f2790c0f01abc95"
dependencies:
object-assign "^4.0.1"
pinkie-promise "^2.0.0"
opn@^5.1.0, opn@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c"
@@ -5228,10 +5138,6 @@ pluralize@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
popper.js@^1.12.9:
version "1.14.4"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.4.tgz#8eec1d8ff02a5a3a152dd43414a15c7b79fd69b6"
portfinder@^1.0.13, portfinder@^1.0.9:
version "1.0.16"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.16.tgz#a6a68be9c352bc66c1a4c17a261f661f3facaf52"
@@ -5702,7 +5608,7 @@ pug-walk@^1.1.7:
version "1.1.7"
resolved "https://registry.yarnpkg.com/pug-walk/-/pug-walk-1.1.7.tgz#c00d5c5128bac5806bec15d2b7e7cdabe42531f3"
pug@^2.0.3:
pug@^2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/pug/-/pug-2.0.3.tgz#71cba82537c95a5eab7ed04696e4221f53aa878e"
dependencies:
@@ -5901,10 +5807,6 @@ regenerate@^1.2.1, regenerate@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
regenerator-runtime@^0.10.0:
version "0.10.5"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1:
version "0.11.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
@@ -6149,10 +6051,6 @@ rx-lite@*, rx-lite@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
rx@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
safe-buffer@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
@@ -6175,13 +6073,21 @@ sax@^1.2.4, sax@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
schema-utils@^0.4.0, schema-utils@^0.4.2, schema-utils@^0.4.3, schema-utils@^0.4.4, schema-utils@^0.4.5:
schema-utils@^0.4.0, schema-utils@^0.4.2, schema-utils@^0.4.4, schema-utils@^0.4.5:
version "0.4.7"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"
dependencies:
ajv "^6.1.0"
ajv-keywords "^3.1.0"
schema-utils@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
dependencies:
ajv "^6.1.0"
ajv-errors "^1.0.0"
ajv-keywords "^3.1.0"
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -6193,8 +6099,8 @@ selfsigned@^1.9.1:
node-forge "0.7.5"
"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
version "5.5.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477"
send@0.16.2:
version "0.16.2"
@@ -6866,8 +6772,8 @@ uglify-to-browserify@~1.0.0:
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
uglifyjs-webpack-plugin@^1.2.4, uglifyjs-webpack-plugin@^1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.7.tgz#57638dd99c853a1ebfe9d97b42160a8a507f9d00"
version "1.3.0"
resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz#75f548160858163a08643e086d5fefe18a5d67de"
dependencies:
cacache "^10.0.4"
find-cache-dir "^1.0.0"
@@ -6953,7 +6859,7 @@ upper-case@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
uri-js@^4.2.1:
uri-js@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
dependencies:
@@ -6971,13 +6877,13 @@ url-join@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a"
url-loader@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-1.0.1.tgz#61bc53f1f184d7343da2728a1289ef8722ea45ee"
url-loader@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-1.1.1.tgz#4d1f3b4f90dde89f02c008e662d604d7511167c1"
dependencies:
loader-utils "^1.1.0"
mime "^2.0.3"
schema-utils "^0.4.3"
schema-utils "^1.0.0"
url-parse@^1.1.8, url-parse@^1.4.3:
version "1.4.3"
@@ -7069,10 +6975,6 @@ void-elements@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
vue-awesome@^2.3.3:
version "2.3.8"
resolved "https://registry.yarnpkg.com/vue-awesome/-/vue-awesome-2.3.8.tgz#95ed4fb7f84453603f0931f865238576e55582a4"
vue-eslint-parser@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz#c268c96c6d94cfe3d938a5f7593959b0ca3360d1"
@@ -7084,10 +6986,6 @@ vue-eslint-parser@^2.0.3:
esquery "^1.0.0"
lodash "^4.17.4"
vue-functional-data-merge@^2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/vue-functional-data-merge/-/vue-functional-data-merge-2.0.6.tgz#f08055adfb92458debcf2ad10c3aa712277f7fc2"
vue-hot-reload-api@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.0.tgz#97976142405d13d8efae154749e88c4e358cf926"
@@ -7102,17 +7000,17 @@ vue-loader@^15.3.0:
vue-hot-reload-api "^2.3.0"
vue-style-loader "^4.1.0"
vue-progressbar@^0.7.5:
vue-progressbar@^0.7.3:
version "0.7.5"
resolved "https://registry.yarnpkg.com/vue-progressbar/-/vue-progressbar-0.7.5.tgz#414730892252b1e45582d4979dec93038e007f79"
vue-router@^3.0.1:
vue-router@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.1.tgz#d9b05ad9c7420ba0f626d6500d693e60092cc1e9"
vue-style-loader@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.1.tgz#7c1d051b24f60b1707602b549ed50b4c8111d316"
version "4.1.2"
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"
dependencies:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
@@ -7132,7 +7030,7 @@ vue-toast@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/vue-toast/-/vue-toast-3.1.0.tgz#19eb4c8150faf5c31c12f8b897a955d1ac0b5e9e"
vue@^2.5.17:
vue@^2.5.15:
version "2.5.17"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.17.tgz#0f8789ad718be68ca1872629832ed533589c6ada"
@@ -7178,8 +7076,8 @@ webpack-bundle-analyzer@^2.13.1:
ws "^4.0.0"
webpack-chain@^4.8.0:
version "4.8.0"
resolved "https://registry.yarnpkg.com/webpack-chain/-/webpack-chain-4.8.0.tgz#06fc3dbb9f2707d4c9e899fc6250fbcf2afe6fd1"
version "4.9.0"
resolved "https://registry.yarnpkg.com/webpack-chain/-/webpack-chain-4.9.0.tgz#2f0794d34d79a7cc5db1416f497b76ad33df30ee"
dependencies:
deepmerge "^1.5.2"
javascript-stringify "^1.6.0"

31
src/sql/16-recurrence.sql Normal file
View File

@@ -0,0 +1,31 @@
ALTER TABLE mpj.request
ADD COLUMN "showAfter" BIGINT NOT NULL DEFAULT 0;
ALTER TABLE mpj.request
ADD COLUMN "recurType" VARCHAR(10) NOT NULL DEFAULT 'immediate';
ALTER TABLE mpj.request
ADD COLUMN "recurCount" SMALLINT NOT NULL DEFAULT 0;
CREATE OR REPLACE VIEW mpj.journal AS
SELECT
request."requestId",
request."userId",
(SELECT "text"
FROM mpj.history
WHERE history."requestId" = request."requestId"
AND "text" IS NOT NULL
ORDER BY "asOf" DESC
LIMIT 1) AS "text",
(SELECT "asOf"
FROM mpj.history
WHERE history."requestId" = request."requestId"
ORDER BY "asOf" DESC
LIMIT 1) AS "asOf",
(SELECT "status"
FROM mpj.history
WHERE history."requestId" = request."requestId"
ORDER BY "asOf" DESC
LIMIT 1) AS "lastStatus",
request."snoozedUntil",
request."showAfter",
request."recurType",
request."recurCount"
FROM mpj.request;