mirror of
https://code.sup39.dev/repos/Wqawg
synced 2024-12-27 15:34:51 +09:00
Allow ticket author to be a remote actor
This commit is contained in:
parent
d73b113b4f
commit
b1897a20c0
26 changed files with 281 additions and 149 deletions
|
@ -281,14 +281,13 @@ Ticket
|
||||||
project ProjectId
|
project ProjectId
|
||||||
number Int
|
number Int
|
||||||
created UTCTime
|
created UTCTime
|
||||||
creator PersonId
|
|
||||||
title Text
|
title Text
|
||||||
source Text -- Pandoc Markdown
|
source Text -- Pandoc Markdown
|
||||||
description Text -- HTML
|
description Text -- HTML
|
||||||
assignee PersonId Maybe
|
assignee PersonId Maybe
|
||||||
status TicketStatus
|
status TicketStatus
|
||||||
closed UTCTime
|
closed UTCTime
|
||||||
closer PersonId
|
closer PersonId Maybe
|
||||||
discuss DiscussionId
|
discuss DiscussionId
|
||||||
followers FollowerSetId
|
followers FollowerSetId
|
||||||
|
|
||||||
|
@ -296,6 +295,18 @@ Ticket
|
||||||
UniqueTicketDiscussion discuss
|
UniqueTicketDiscussion discuss
|
||||||
UniqueTicketFollowers followers
|
UniqueTicketFollowers followers
|
||||||
|
|
||||||
|
TicketAuthorLocal
|
||||||
|
ticket TicketId
|
||||||
|
author PersonId
|
||||||
|
|
||||||
|
UniqueTicketAuthorLocal ticket
|
||||||
|
|
||||||
|
TicketAuthorRemote
|
||||||
|
ticket TicketId
|
||||||
|
author RemoteActorId
|
||||||
|
|
||||||
|
UniqueTicketAuthorRemote ticket
|
||||||
|
|
||||||
TicketDependency
|
TicketDependency
|
||||||
parent TicketId
|
parent TicketId
|
||||||
child TicketId
|
child TicketId
|
||||||
|
|
11
migrations/2019_06_06.model
Normal file
11
migrations/2019_06_06.model
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
TicketAuthorLocal
|
||||||
|
ticket TicketId
|
||||||
|
author PersonId
|
||||||
|
|
||||||
|
UniqueTicketAuthorLocal ticket
|
||||||
|
|
||||||
|
TicketAuthorRemote
|
||||||
|
ticket TicketId
|
||||||
|
author RemoteActorId
|
||||||
|
|
||||||
|
UniqueTicketAuthorRemote ticket
|
24
migrations/2019_06_06_mig.model
Normal file
24
migrations/2019_06_06_mig.model
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
TicketAuthorLocal
|
||||||
|
ticket TicketId
|
||||||
|
author Int64
|
||||||
|
|
||||||
|
UniqueTicketAuthorLocal ticket
|
||||||
|
|
||||||
|
Ticket
|
||||||
|
project Int64
|
||||||
|
number Int
|
||||||
|
created UTCTime
|
||||||
|
creator Int64
|
||||||
|
title Text
|
||||||
|
source Text -- Pandoc Markdown
|
||||||
|
description Text -- HTML
|
||||||
|
assignee Int64 Maybe
|
||||||
|
status Text
|
||||||
|
closed UTCTime
|
||||||
|
closer Int64
|
||||||
|
discuss Int64
|
||||||
|
followers Int64
|
||||||
|
|
||||||
|
UniqueTicket project number
|
||||||
|
UniqueTicketDiscussion discuss
|
||||||
|
UniqueTicketFollowers followers
|
|
@ -17,11 +17,14 @@ module Data.Either.Local
|
||||||
( maybeRight
|
( maybeRight
|
||||||
, maybeLeft
|
, maybeLeft
|
||||||
, requireEither
|
, requireEither
|
||||||
|
, requireEitherAlt
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Prelude
|
import Prelude
|
||||||
|
|
||||||
|
import Control.Applicative
|
||||||
|
|
||||||
maybeRight :: Either a b -> Maybe b
|
maybeRight :: Either a b -> Maybe b
|
||||||
maybeRight (Left _) = Nothing
|
maybeRight (Left _) = Nothing
|
||||||
maybeRight (Right b) = Just b
|
maybeRight (Right b) = Just b
|
||||||
|
@ -35,3 +38,13 @@ requireEither Nothing Nothing = Left False
|
||||||
requireEither (Just _) (Just _) = Left True
|
requireEither (Just _) (Just _) = Left True
|
||||||
requireEither (Just x) Nothing = Right $ Left x
|
requireEither (Just x) Nothing = Right $ Left x
|
||||||
requireEither Nothing (Just y) = Right $ Right y
|
requireEither Nothing (Just y) = Right $ Right y
|
||||||
|
|
||||||
|
requireEitherAlt
|
||||||
|
:: Applicative f
|
||||||
|
=> f (Maybe a) -> f (Maybe b) -> String -> String -> f (Either a b)
|
||||||
|
requireEitherAlt get1 get2 errNone errBoth = liftA2 mk get1 get2
|
||||||
|
where
|
||||||
|
mk Nothing Nothing = error errNone
|
||||||
|
mk (Just _) (Just _) = error errBoth
|
||||||
|
mk (Just x) Nothing = Left x
|
||||||
|
mk Nothing (Just y) = Right y
|
||||||
|
|
|
@ -123,7 +123,6 @@ editTicketContentAForm ticket = Ticket
|
||||||
<$> pure (ticketProject ticket)
|
<$> pure (ticketProject ticket)
|
||||||
<*> pure (ticketNumber ticket)
|
<*> pure (ticketNumber ticket)
|
||||||
<*> pure (ticketCreated ticket)
|
<*> pure (ticketCreated ticket)
|
||||||
<*> pure (ticketCreator ticket)
|
|
||||||
<*> areq textField "Title*" (Just $ ticketTitle ticket)
|
<*> areq textField "Title*" (Just $ ticketTitle ticket)
|
||||||
<*> ( maybe "" (T.filter (/= '\r') . unTextarea) <$>
|
<*> ( maybe "" (T.filter (/= '\r') . unTextarea) <$>
|
||||||
aopt
|
aopt
|
||||||
|
|
|
@ -52,7 +52,7 @@ import Vervis.Model.Group
|
||||||
import Vervis.Model.Ident (ShrIdent, shr2text)
|
import Vervis.Model.Ident (ShrIdent, shr2text)
|
||||||
import Vervis.Settings (widgetFile)
|
import Vervis.Settings (widgetFile)
|
||||||
import Vervis.Time (showDate)
|
import Vervis.Time (showDate)
|
||||||
import Vervis.Widget.Sharer (groupLinkW, personLinkW)
|
import Vervis.Widget.Sharer
|
||||||
|
|
||||||
getGroupsR :: Handler Html
|
getGroupsR :: Handler Html
|
||||||
getGroupsR = do
|
getGroupsR = do
|
||||||
|
|
|
@ -78,7 +78,7 @@ import Vervis.Style
|
||||||
import Vervis.Time (showDate)
|
import Vervis.Time (showDate)
|
||||||
import Vervis.Widget (buttonW)
|
import Vervis.Widget (buttonW)
|
||||||
import Vervis.Widget.Repo
|
import Vervis.Widget.Repo
|
||||||
import Vervis.Widget.Sharer (personLinkW)
|
import Vervis.Widget.Sharer
|
||||||
|
|
||||||
import qualified Data.ByteString.Lazy as BL (ByteString)
|
import qualified Data.ByteString.Lazy as BL (ByteString)
|
||||||
import qualified Data.Git.Local as G (createRepo)
|
import qualified Data.Git.Local as G (createRepo)
|
||||||
|
|
|
@ -96,6 +96,7 @@ import Yesod.Hashids
|
||||||
|
|
||||||
import qualified Web.ActivityPub as AP
|
import qualified Web.ActivityPub as AP
|
||||||
|
|
||||||
|
import Data.Either.Local
|
||||||
import Data.Maybe.Local (partitionMaybePairs)
|
import Data.Maybe.Local (partitionMaybePairs)
|
||||||
import Database.Persist.Local
|
import Database.Persist.Local
|
||||||
import Yesod.Persist.Local
|
import Yesod.Persist.Local
|
||||||
|
@ -117,7 +118,7 @@ import Vervis.TicketFilter (filterTickets)
|
||||||
import Vervis.Time (showDate)
|
import Vervis.Time (showDate)
|
||||||
import Vervis.Widget (buttonW)
|
import Vervis.Widget (buttonW)
|
||||||
import Vervis.Widget.Discussion (discussionW)
|
import Vervis.Widget.Discussion (discussionW)
|
||||||
import Vervis.Widget.Sharer (personLinkW)
|
import Vervis.Widget.Sharer
|
||||||
import Vervis.Widget.Ticket
|
import Vervis.Widget.Ticket
|
||||||
|
|
||||||
getTicketsR :: ShrIdent -> PrjIdent -> Handler Html
|
getTicketsR :: ShrIdent -> PrjIdent -> Handler Html
|
||||||
|
@ -164,18 +165,18 @@ postTicketsR shar proj = do
|
||||||
{ ticketProject = pid
|
{ ticketProject = pid
|
||||||
, ticketNumber = projectNextTicket project
|
, ticketNumber = projectNextTicket project
|
||||||
, ticketCreated = now
|
, ticketCreated = now
|
||||||
, ticketCreator = author
|
|
||||||
, ticketTitle = ntTitle nt
|
, ticketTitle = ntTitle nt
|
||||||
, ticketSource = source
|
, ticketSource = source
|
||||||
, ticketDescription = descHtml
|
, ticketDescription = descHtml
|
||||||
, ticketAssignee = Nothing
|
, ticketAssignee = Nothing
|
||||||
, ticketStatus = TSNew
|
, ticketStatus = TSNew
|
||||||
, ticketClosed = UTCTime (ModifiedJulianDay 0) 0
|
, ticketClosed = UTCTime (ModifiedJulianDay 0) 0
|
||||||
, ticketCloser = author
|
, ticketCloser = Nothing
|
||||||
, ticketDiscuss = did
|
, ticketDiscuss = did
|
||||||
, ticketFollowers = fsid
|
, ticketFollowers = fsid
|
||||||
}
|
}
|
||||||
tid <- insert ticket
|
tid <- insert ticket
|
||||||
|
insert_ $ TicketAuthorLocal tid author
|
||||||
let mktparam (fid, v) = TicketParamText
|
let mktparam (fid, v) = TicketParamText
|
||||||
{ ticketParamTextTicket = tid
|
{ ticketParamTextTicket = tid
|
||||||
, ticketParamTextField = fid
|
, ticketParamTextField = fid
|
||||||
|
@ -221,7 +222,7 @@ getTicketR :: ShrIdent -> PrjIdent -> Int -> Handler TypedContent
|
||||||
getTicketR shar proj num = do
|
getTicketR shar proj num = do
|
||||||
mpid <- maybeAuthId
|
mpid <- maybeAuthId
|
||||||
( wshr, wfl,
|
( wshr, wfl,
|
||||||
author, massignee, closer, ticket, tparams, eparams, deps, rdeps) <-
|
author, massignee, mcloser, ticket, tparams, eparams, deps, rdeps) <-
|
||||||
runDB $ do
|
runDB $ do
|
||||||
(jid, wshr, wid, wfl) <- do
|
(jid, wshr, wid, wfl) <- do
|
||||||
Entity s sharer <- getBy404 $ UniqueSharer shar
|
Entity s sharer <- getBy404 $ UniqueSharer shar
|
||||||
|
@ -238,19 +239,37 @@ getTicketR shar proj num = do
|
||||||
, workflowIdent w
|
, workflowIdent w
|
||||||
)
|
)
|
||||||
Entity tid ticket <- getBy404 $ UniqueTicket jid num
|
Entity tid ticket <- getBy404 $ UniqueTicket jid num
|
||||||
author <- do
|
author <-
|
||||||
person <- get404 $ ticketCreator ticket
|
requireEitherAlt
|
||||||
get404 $ personIdent person
|
(do mtal <- getValBy $ UniqueTicketAuthorLocal tid
|
||||||
|
for mtal $ \ tal -> do
|
||||||
|
p <- getJust $ ticketAuthorLocalAuthor tal
|
||||||
|
getJust $ personIdent p
|
||||||
|
)
|
||||||
|
(do mtar <- getValBy $ UniqueTicketAuthorRemote tid
|
||||||
|
for mtar $ \ tar -> do
|
||||||
|
ra <- getJust $ ticketAuthorRemoteAuthor tar
|
||||||
|
i <- getJust $ remoteActorInstance ra
|
||||||
|
return (i, ra)
|
||||||
|
)
|
||||||
|
"Ticket doesn't have author"
|
||||||
|
"Ticket has both local and remote author"
|
||||||
massignee <- for (ticketAssignee ticket) $ \ apid -> do
|
massignee <- for (ticketAssignee ticket) $ \ apid -> do
|
||||||
person <- get404 apid
|
person <- get404 apid
|
||||||
sharer <- get404 $ personIdent person
|
sharer <- get404 $ personIdent person
|
||||||
return (sharer, fromMaybe False $ (== apid) <$> mpid)
|
return (sharer, fromMaybe False $ (== apid) <$> mpid)
|
||||||
closer <-
|
mcloser <-
|
||||||
case ticketStatus ticket of
|
case ticketStatus ticket of
|
||||||
TSClosed -> do
|
TSClosed ->
|
||||||
person <- get404 $ ticketCloser ticket
|
case ticketCloser ticket of
|
||||||
get404 $ personIdent person
|
Just pidCloser -> Just <$> do
|
||||||
_ -> return author
|
person <- getJust pidCloser
|
||||||
|
getJust $ personIdent person
|
||||||
|
Nothing -> error "Closer not set for closed ticket"
|
||||||
|
_ ->
|
||||||
|
case ticketCloser ticket of
|
||||||
|
Just _ -> error "Closer set for open ticket"
|
||||||
|
Nothing -> return Nothing
|
||||||
tparams <- getTicketTextParams tid wid
|
tparams <- getTicketTextParams tid wid
|
||||||
eparams <- getTicketEnumParams tid wid
|
eparams <- getTicketEnumParams tid wid
|
||||||
deps <- E.select $ E.from $ \ (dep `E.InnerJoin` t) -> do
|
deps <- E.select $ E.from $ \ (dep `E.InnerJoin` t) -> do
|
||||||
|
@ -263,7 +282,7 @@ getTicketR shar proj num = do
|
||||||
return t
|
return t
|
||||||
return
|
return
|
||||||
( wshr, wfl
|
( wshr, wfl
|
||||||
, author, massignee, closer, ticket, tparams, eparams
|
, author, massignee, mcloser, ticket, tparams, eparams
|
||||||
, deps, rdeps
|
, deps, rdeps
|
||||||
)
|
)
|
||||||
encodeHid <- getEncodeKeyHashid
|
encodeHid <- getEncodeKeyHashid
|
||||||
|
@ -287,6 +306,10 @@ getTicketR shar proj num = do
|
||||||
encodeRouteHome <- getEncodeRouteHome
|
encodeRouteHome <- getEncodeRouteHome
|
||||||
let siblingUri =
|
let siblingUri =
|
||||||
encodeRouteHome . TicketR shar proj . ticketNumber . entityVal
|
encodeRouteHome . TicketR shar proj . ticketNumber . entityVal
|
||||||
|
host =
|
||||||
|
case author of
|
||||||
|
Left _ -> hLocal
|
||||||
|
Right (i, _) -> instanceHost i
|
||||||
ticketAP = AP.Ticket
|
ticketAP = AP.Ticket
|
||||||
{ AP.ticketLocal = Just
|
{ AP.ticketLocal = Just
|
||||||
( hLocal
|
( hLocal
|
||||||
|
@ -307,7 +330,11 @@ getTicketR shar proj num = do
|
||||||
)
|
)
|
||||||
|
|
||||||
, AP.ticketAttributedTo =
|
, AP.ticketAttributedTo =
|
||||||
encodeRouteLocal $ SharerR $ sharerIdent author
|
case author of
|
||||||
|
Left sharer ->
|
||||||
|
encodeRouteLocal $ SharerR $ sharerIdent sharer
|
||||||
|
Right (_inztance, actor) ->
|
||||||
|
remoteActorIdent actor
|
||||||
, AP.ticketPublished = Just $ ticketCreated ticket
|
, AP.ticketPublished = Just $ ticketCreated ticket
|
||||||
, AP.ticketUpdated = Nothing
|
, AP.ticketUpdated = Nothing
|
||||||
, AP.ticketName = Just $ "#" <> T.pack (show num)
|
, AP.ticketName = Just $ "#" <> T.pack (show num)
|
||||||
|
@ -322,7 +349,7 @@ getTicketR shar proj num = do
|
||||||
, AP.ticketDependsOn = map siblingUri deps
|
, AP.ticketDependsOn = map siblingUri deps
|
||||||
, AP.ticketDependedBy = map siblingUri rdeps
|
, AP.ticketDependedBy = map siblingUri rdeps
|
||||||
}
|
}
|
||||||
provideHtmlAndAP ticketAP $(widgetFile "ticket/one")
|
provideHtmlAndAP' host ticketAP $(widgetFile "ticket/one")
|
||||||
|
|
||||||
putTicketR :: ShrIdent -> PrjIdent -> Int -> Handler Html
|
putTicketR :: ShrIdent -> PrjIdent -> Int -> Handler Html
|
||||||
putTicketR shar proj num = do
|
putTicketR shar proj num = do
|
||||||
|
@ -438,7 +465,7 @@ postTicketCloseR shr prj num = do
|
||||||
[ TicketAssignee =. Nothing
|
[ TicketAssignee =. Nothing
|
||||||
, TicketStatus =. TSClosed
|
, TicketStatus =. TSClosed
|
||||||
, TicketClosed =. now
|
, TicketClosed =. now
|
||||||
, TicketCloser =. pid
|
, TicketCloser =. Just pid
|
||||||
]
|
]
|
||||||
return True
|
return True
|
||||||
setMessage $
|
setMessage $
|
||||||
|
@ -460,7 +487,7 @@ postTicketOpenR shr prj num = do
|
||||||
TSClosed -> do
|
TSClosed -> do
|
||||||
update tid
|
update tid
|
||||||
[ TicketStatus =. TSTodo
|
[ TicketStatus =. TSTodo
|
||||||
, TicketCloser =. ticketCreator ticket
|
, TicketCloser =. Nothing
|
||||||
]
|
]
|
||||||
return True
|
return True
|
||||||
_ -> return False
|
_ -> return False
|
||||||
|
@ -768,24 +795,42 @@ getTicketDeps forward shr prj num = do
|
||||||
Entity sid _ <- getBy404 $ UniqueSharer shr
|
Entity sid _ <- getBy404 $ UniqueSharer shr
|
||||||
Entity jid _ <- getBy404 $ UniqueProject prj sid
|
Entity jid _ <- getBy404 $ UniqueProject prj sid
|
||||||
Entity tid _ <- getBy404 $ UniqueTicket jid num
|
Entity tid _ <- getBy404 $ UniqueTicket jid num
|
||||||
E.select $ E.from $
|
fmap (map toRow) $ E.select $ E.from $
|
||||||
\ ( td `E.InnerJoin`
|
\ ( td
|
||||||
ticket `E.InnerJoin`
|
`E.InnerJoin` t
|
||||||
person `E.InnerJoin`
|
`E.LeftOuterJoin` (tal `E.InnerJoin` p `E.InnerJoin` s)
|
||||||
sharer
|
`E.LeftOuterJoin` (tar `E.InnerJoin` ra `E.InnerJoin` i)
|
||||||
) -> do
|
) -> do
|
||||||
E.on $ person E.^. PersonIdent E.==. sharer E.^. SharerId
|
E.on $ ra E.?. RemoteActorInstance E.==. i E.?. InstanceId
|
||||||
E.on $ ticket E.^. TicketCreator E.==. person E.^. PersonId
|
E.on $ tar E.?. TicketAuthorRemoteAuthor E.==. ra E.?. RemoteActorId
|
||||||
E.on $ td E.^. to' E.==. ticket E.^. TicketId
|
E.on $ E.just (t E.^. TicketId) E.==. tar E.?. TicketAuthorRemoteTicket
|
||||||
|
E.on $ p E.?. PersonIdent E.==. s E.?. SharerId
|
||||||
|
E.on $ tal E.?. TicketAuthorLocalAuthor E.==. p E.?. PersonId
|
||||||
|
E.on $ E.just (t E.^. TicketId) E.==. tal E.?. TicketAuthorLocalTicket
|
||||||
|
E.on $ td E.^. to' E.==. t E.^. TicketId
|
||||||
E.where_ $ td E.^. from' E.==. E.val tid
|
E.where_ $ td E.^. from' E.==. E.val tid
|
||||||
E.orderBy [E.asc $ ticket E.^. TicketNumber]
|
E.orderBy [E.asc $ t E.^. TicketNumber]
|
||||||
return
|
return
|
||||||
( ticket E.^. TicketNumber
|
( t E.^. TicketNumber
|
||||||
, sharer
|
, s
|
||||||
, ticket E.^. TicketTitle
|
, i
|
||||||
, ticket E.^. TicketStatus
|
, ra
|
||||||
|
, t E.^. TicketTitle
|
||||||
|
, t E.^. TicketStatus
|
||||||
)
|
)
|
||||||
defaultLayout $(widgetFile "ticket/dep/list")
|
defaultLayout $(widgetFile "ticket/dep/list")
|
||||||
|
where
|
||||||
|
toRow (E.Value number, ms, mi, mra, E.Value title, E.Value status) =
|
||||||
|
( number
|
||||||
|
, case (ms, mi, mra) of
|
||||||
|
(Just s, Nothing, Nothing) ->
|
||||||
|
Left $ entityVal s
|
||||||
|
(Nothing, Just i, Just ra) ->
|
||||||
|
Right (entityVal i, entityVal ra)
|
||||||
|
_ -> error "Ticket author DB invalid state"
|
||||||
|
, title
|
||||||
|
, status
|
||||||
|
)
|
||||||
|
|
||||||
getTicketDepsR :: ShrIdent -> PrjIdent -> Int -> Handler Html
|
getTicketDepsR :: ShrIdent -> PrjIdent -> Int -> Handler Html
|
||||||
getTicketDepsR = getTicketDeps True
|
getTicketDepsR = getTicketDeps True
|
||||||
|
@ -934,16 +979,6 @@ getTicketTeamR shr prj num = do
|
||||||
[whamlet|
|
[whamlet|
|
||||||
<div><pre>#{encodePrettyToLazyText doc}
|
<div><pre>#{encodePrettyToLazyText doc}
|
||||||
|]
|
|]
|
||||||
where
|
|
||||||
requireEitherAlt
|
|
||||||
:: Applicative f
|
|
||||||
=> f (Maybe a) -> f (Maybe b) -> String -> String -> f (Either a b)
|
|
||||||
requireEitherAlt get1 get2 errNone errBoth = liftA2 mk get1 get2
|
|
||||||
where
|
|
||||||
mk Nothing Nothing = error errNone
|
|
||||||
mk (Just _) (Just _) = error errBoth
|
|
||||||
mk (Just x) Nothing = Left x
|
|
||||||
mk Nothing (Just y) = Right y
|
|
||||||
|
|
||||||
getTicketEventsR :: ShrIdent -> PrjIdent -> Int -> Handler TypedContent
|
getTicketEventsR :: ShrIdent -> PrjIdent -> Int -> Handler TypedContent
|
||||||
getTicketEventsR shr prj num = error "TODO not implemented"
|
getTicketEventsR shr prj num = error "TODO not implemented"
|
||||||
|
|
|
@ -527,6 +527,18 @@ changes hLocal ctx =
|
||||||
[ Ticket201906Source =. source
|
[ Ticket201906Source =. source
|
||||||
, Ticket201906Description =. content
|
, Ticket201906Description =. content
|
||||||
]
|
]
|
||||||
|
-- 91
|
||||||
|
, addEntities model_2019_06_06
|
||||||
|
-- 92
|
||||||
|
, unchecked $ lift $ do
|
||||||
|
tickets <- selectList ([] :: [Filter Ticket20190606]) []
|
||||||
|
let mklocal (Entity tid t) =
|
||||||
|
TicketAuthorLocal20190606 tid $ ticket20190606Creator t
|
||||||
|
insertMany_ $ map mklocal tickets
|
||||||
|
-- 93
|
||||||
|
, setFieldMaybe "Ticket" "closer"
|
||||||
|
-- 94
|
||||||
|
, removeField "Ticket" "creator"
|
||||||
]
|
]
|
||||||
|
|
||||||
migrateDB :: MonadIO m => Text -> HashidsContext -> ReaderT SqlBackend m (Either Text (Int, Int))
|
migrateDB :: MonadIO m => Text -> HashidsContext -> ReaderT SqlBackend m (Either Text (Int, Int))
|
||||||
|
|
|
@ -57,6 +57,10 @@ module Vervis.Migration.Model
|
||||||
, Message201906
|
, Message201906
|
||||||
, Ticket201906Generic (..)
|
, Ticket201906Generic (..)
|
||||||
, Ticket201906
|
, Ticket201906
|
||||||
|
, model_2019_06_06
|
||||||
|
, Ticket20190606Generic (..)
|
||||||
|
, Ticket20190606
|
||||||
|
, TicketAuthorLocal20190606Generic (..)
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
@ -146,3 +150,9 @@ makeEntitiesMigration "201906"
|
||||||
|
|
||||||
makeEntitiesMigration "201906"
|
makeEntitiesMigration "201906"
|
||||||
$(modelFile "migrations/2019_06_03.model")
|
$(modelFile "migrations/2019_06_03.model")
|
||||||
|
|
||||||
|
model_2019_06_06 :: [Entity SqlBackend]
|
||||||
|
model_2019_06_06 = $(schema "2019_06_06")
|
||||||
|
|
||||||
|
makeEntitiesMigration "20190606"
|
||||||
|
$(modelFile "migrations/2019_06_06_mig.model")
|
||||||
|
|
|
@ -47,34 +47,51 @@ getTicketSummaries
|
||||||
-> Maybe (SqlExpr (Entity Ticket) -> [SqlExpr OrderBy])
|
-> Maybe (SqlExpr (Entity Ticket) -> [SqlExpr OrderBy])
|
||||||
-> ProjectId
|
-> ProjectId
|
||||||
-> AppDB [TicketSummary]
|
-> AppDB [TicketSummary]
|
||||||
getTicketSummaries mfilt morder jid = do
|
getTicketSummaries mfilt morder jid = fmap (map toSummary) $ select $ from $
|
||||||
let toSummary (Value n, Entity _ s, Value c, Value t, Value d, Value r) =
|
\ ( t
|
||||||
TicketSummary
|
`LeftOuterJoin` (tal `InnerJoin` p `InnerJoin` s)
|
||||||
{ tsNumber = n
|
`LeftOuterJoin` (tar `InnerJoin` ra `InnerJoin` i)
|
||||||
, tsCreatedBy = s
|
`InnerJoin` d
|
||||||
, tsCreatedAt = c
|
`LeftOuterJoin` m
|
||||||
, tsTitle = t
|
) -> do
|
||||||
, tsStatus = d
|
|
||||||
, tsComments = r
|
|
||||||
}
|
|
||||||
fmap (map toSummary) $ select $ from $
|
|
||||||
\ (t `InnerJoin` p `InnerJoin` s `InnerJoin` d `LeftOuterJoin` m) -> do
|
|
||||||
on $ just (d ^. DiscussionId) ==. m ?. MessageRoot
|
on $ just (d ^. DiscussionId) ==. m ?. MessageRoot
|
||||||
on $ t ^. TicketDiscuss ==. d ^. DiscussionId
|
on $ t ^. TicketDiscuss ==. d ^. DiscussionId
|
||||||
on $ p ^. PersonIdent ==. s ^. SharerId
|
on $ ra ?. RemoteActorInstance ==. i ?. InstanceId
|
||||||
on $ t ^. TicketCreator ==. p ^. PersonId
|
on $ tar ?. TicketAuthorRemoteAuthor ==. ra ?. RemoteActorId
|
||||||
|
on $ just (t ^. TicketId) ==. tar ?. TicketAuthorRemoteTicket
|
||||||
|
on $ p ?. PersonIdent ==. s ?. SharerId
|
||||||
|
on $ tal ?. TicketAuthorLocalAuthor ==. p ?. PersonId
|
||||||
|
on $ just (t ^. TicketId) ==. tal ?. TicketAuthorLocalTicket
|
||||||
where_ $ t ^. TicketProject ==. val jid
|
where_ $ t ^. TicketProject ==. val jid
|
||||||
groupBy (t ^. TicketId, s ^. SharerId)
|
groupBy $ t ^. TicketId
|
||||||
for_ mfilt $ \ filt -> where_ $ filt t
|
for_ mfilt $ \ filt -> where_ $ filt t
|
||||||
for_ morder $ \ order -> orderBy $ order t
|
for_ morder $ \ order -> orderBy $ order t
|
||||||
return
|
return
|
||||||
( t ^. TicketNumber
|
( t ^. TicketNumber
|
||||||
, s
|
, s
|
||||||
|
, i
|
||||||
|
, ra
|
||||||
, t ^. TicketCreated
|
, t ^. TicketCreated
|
||||||
, t ^. TicketTitle
|
, t ^. TicketTitle
|
||||||
, t ^. TicketStatus
|
, t ^. TicketStatus
|
||||||
, count $ m ?. MessageId
|
, count $ m ?. MessageId
|
||||||
)
|
)
|
||||||
|
where
|
||||||
|
toSummary (Value n, ms, mi, mra, Value c, Value t, Value d, Value r) =
|
||||||
|
TicketSummary
|
||||||
|
{ tsNumber = n
|
||||||
|
, tsCreatedBy =
|
||||||
|
case (ms, mi, mra) of
|
||||||
|
(Just s, Nothing, Nothing) ->
|
||||||
|
Left $ entityVal s
|
||||||
|
(Nothing, Just i, Just ra) ->
|
||||||
|
Right (entityVal i, entityVal ra)
|
||||||
|
_ -> error "Ticket author DB invalid state"
|
||||||
|
, tsCreatedAt = c
|
||||||
|
, tsTitle = t
|
||||||
|
, tsStatus = d
|
||||||
|
, tsComments = r
|
||||||
|
}
|
||||||
|
|
||||||
-- | Get the child-parent ticket number pairs of all the ticket dependencies
|
-- | Get the child-parent ticket number pairs of all the ticket dependencies
|
||||||
-- in the given project, in ascending order by child, and then ascending order
|
-- in the given project, in ascending order by child, and then ascending order
|
||||||
|
|
|
@ -45,7 +45,7 @@ import Vervis.Model
|
||||||
import Vervis.Model.Ident
|
import Vervis.Model.Ident
|
||||||
import Vervis.Render (renderSourceT)
|
import Vervis.Render (renderSourceT)
|
||||||
import Vervis.Settings (widgetFile)
|
import Vervis.Settings (widgetFile)
|
||||||
import Vervis.Widget.Sharer (personLinkW)
|
import Vervis.Widget.Sharer
|
||||||
|
|
||||||
actorLinkW :: MessageTreeNodeAuthor -> Widget
|
actorLinkW :: MessageTreeNodeAuthor -> Widget
|
||||||
actorLinkW actor = $(widgetFile "widget/actor-link")
|
actorLinkW actor = $(widgetFile "widget/actor-link")
|
||||||
|
|
|
@ -15,28 +15,40 @@
|
||||||
|
|
||||||
module Vervis.Widget.Sharer
|
module Vervis.Widget.Sharer
|
||||||
( sharerLinkW
|
( sharerLinkW
|
||||||
, personLinkW
|
, sharerLinkFedW
|
||||||
, groupLinkW
|
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Prelude
|
import Prelude
|
||||||
|
|
||||||
import Yesod.Core (Route)
|
import Yesod.Core
|
||||||
|
|
||||||
|
import Network.FedURI
|
||||||
|
|
||||||
import Vervis.Foundation
|
import Vervis.Foundation
|
||||||
import Vervis.Model
|
import Vervis.Model
|
||||||
import Vervis.Model.Ident (ShrIdent, shr2text)
|
import Vervis.Model.Ident (ShrIdent, shr2text)
|
||||||
import Vervis.Settings (widgetFile)
|
import Vervis.Settings (widgetFile)
|
||||||
|
|
||||||
link :: (ShrIdent -> Route App) -> Sharer -> Widget
|
|
||||||
link route sharer = $(widgetFile "sharer-link")
|
|
||||||
|
|
||||||
sharerLinkW :: Sharer -> Widget
|
sharerLinkW :: Sharer -> Widget
|
||||||
sharerLinkW = link SharerR
|
sharerLinkW sharer =
|
||||||
|
[whamlet|
|
||||||
|
<a href=@{SharerR $ sharerIdent sharer}>
|
||||||
|
$maybe name <- sharerName sharer
|
||||||
|
#{name}
|
||||||
|
$nothing
|
||||||
|
#{shr2text $ sharerIdent sharer}
|
||||||
|
|]
|
||||||
|
|
||||||
personLinkW :: Sharer -> Widget
|
sharerLinkFedW :: Either Sharer (Instance, RemoteActor) -> Widget
|
||||||
personLinkW = link SharerR
|
sharerLinkFedW (Left sharer) = sharerLinkW sharer
|
||||||
|
sharerLinkFedW (Right (inztance, actor)) =
|
||||||
groupLinkW :: Sharer -> Widget
|
[whamlet|
|
||||||
groupLinkW = link SharerR
|
<a href="#{renderFedURI uActor}">
|
||||||
|
$maybe name <- remoteActorName actor
|
||||||
|
#{name}
|
||||||
|
$nothing
|
||||||
|
(?)
|
||||||
|
|]
|
||||||
|
where
|
||||||
|
uActor = l2f (instanceHost inztance) (remoteActorIdent actor)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
-
|
-
|
||||||
- ♡ Copying is an act of love. Please copy, reuse and share.
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
-
|
-
|
||||||
|
@ -45,11 +45,11 @@ import Vervis.Model.Ticket
|
||||||
import Vervis.Settings (widgetFile)
|
import Vervis.Settings (widgetFile)
|
||||||
import Vervis.Style
|
import Vervis.Style
|
||||||
import Vervis.Time (showDate)
|
import Vervis.Time (showDate)
|
||||||
import Vervis.Widget.Sharer (personLinkW)
|
import Vervis.Widget.Sharer
|
||||||
|
|
||||||
data TicketSummary = TicketSummary
|
data TicketSummary = TicketSummary
|
||||||
{ tsNumber :: Int
|
{ tsNumber :: Int
|
||||||
, tsCreatedBy :: Sharer
|
, tsCreatedBy :: Either Sharer (Instance, RemoteActor)
|
||||||
, tsCreatedAt :: UTCTime
|
, tsCreatedAt :: UTCTime
|
||||||
, tsTitle :: Text
|
, tsTitle :: Text
|
||||||
, tsStatus :: TicketStatus
|
, tsStatus :: TicketStatus
|
||||||
|
|
|
@ -18,6 +18,7 @@ module Yesod.ActivityPub
|
||||||
, deliverActivity
|
, deliverActivity
|
||||||
, forwardActivity
|
, forwardActivity
|
||||||
, provideHtmlAndAP
|
, provideHtmlAndAP
|
||||||
|
, provideHtmlAndAP'
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
@ -121,8 +122,13 @@ provideHtmlAndAP
|
||||||
=> a -> WidgetFor site () -> HandlerFor site TypedContent
|
=> a -> WidgetFor site () -> HandlerFor site TypedContent
|
||||||
provideHtmlAndAP object widget = do
|
provideHtmlAndAP object widget = do
|
||||||
host <- getsYesod siteInstanceHost
|
host <- getsYesod siteInstanceHost
|
||||||
|
provideHtmlAndAP' host object widget
|
||||||
|
|
||||||
|
provideHtmlAndAP'
|
||||||
|
:: (YesodActivityPub site, ActivityPub a)
|
||||||
|
=> Text -> a -> WidgetFor site () -> HandlerFor site TypedContent
|
||||||
|
provideHtmlAndAP' host object widget = selectRep $ do
|
||||||
let doc = Doc host object
|
let doc = Doc host object
|
||||||
selectRep $ do
|
|
||||||
provideAP $ pure doc
|
provideAP $ pure doc
|
||||||
provideRep $ do
|
provideRep $ do
|
||||||
mval <- lookupGetParam "prettyjson"
|
mval <- lookupGetParam "prettyjson"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
$# This file is part of Vervis.
|
$# This file is part of Vervis.
|
||||||
$#
|
$#
|
||||||
$# Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
$# Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
$#
|
$#
|
||||||
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
$#
|
$#
|
||||||
|
@ -18,4 +18,4 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
<ul>
|
<ul>
|
||||||
$forall Entity _sid sharer <- groups
|
$forall Entity _sid sharer <- groups
|
||||||
<li>
|
<li>
|
||||||
^{groupLinkW sharer}
|
^{sharerLinkW sharer}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
$# This file is part of Vervis.
|
$# This file is part of Vervis.
|
||||||
$#
|
$#
|
||||||
$# Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
$# Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
$#
|
$#
|
||||||
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
$#
|
$#
|
||||||
|
@ -24,4 +24,4 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
<ul>
|
<ul>
|
||||||
$forall Entity _sid s <- members
|
$forall Entity _sid s <- members
|
||||||
<li>
|
<li>
|
||||||
^{personLinkW s}
|
^{sharerLinkW s}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
$# This file is part of Vervis.
|
$# This file is part of Vervis.
|
||||||
$#
|
$#
|
||||||
$# Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
$# Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
$#
|
$#
|
||||||
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
$#
|
$#
|
||||||
|
@ -23,7 +23,7 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
<td>
|
<td>
|
||||||
#{showDate time}
|
#{showDate time}
|
||||||
<td>
|
<td>
|
||||||
^{personLinkW sharer}
|
^{sharerLinkW sharer}
|
||||||
<td>
|
<td>
|
||||||
<a href=@{TicketR shr prj num}>#{num}
|
<a href=@{TicketR shr prj num}>#{num}
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -18,7 +18,7 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
<th>Role
|
<th>Role
|
||||||
$forall (Entity _sid sharer, Value mrl) <- devs
|
$forall (Entity _sid sharer, Value mrl) <- devs
|
||||||
<tr>
|
<tr>
|
||||||
<td>^{personLinkW sharer}
|
<td>^{sharerLinkW sharer}
|
||||||
<td>
|
<td>
|
||||||
$maybe rl <- mrl
|
$maybe rl <- mrl
|
||||||
#{rl2text rl}
|
#{rl2text rl}
|
||||||
|
|
|
@ -18,7 +18,7 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
<th>Role
|
<th>Role
|
||||||
$forall (Entity _sid sharer, Value mrl) <- devs
|
$forall (Entity _sid sharer, Value mrl) <- devs
|
||||||
<tr>
|
<tr>
|
||||||
<td>^{personLinkW sharer}
|
<td>^{sharerLinkW sharer}
|
||||||
<td>
|
<td>
|
||||||
$maybe rl <- mrl
|
$maybe rl <- mrl
|
||||||
#{rl2text rl}
|
#{rl2text rl}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
$# This file is part of Vervis.
|
$# This file is part of Vervis.
|
||||||
$#
|
$#
|
||||||
$# Written in 2018 by fr33domlover <fr33domlover@riseup.net>.
|
$# Written in 2018, 2019 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
$#
|
$#
|
||||||
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
$#
|
$#
|
||||||
|
@ -17,7 +17,7 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
<td>By
|
<td>By
|
||||||
<td>
|
<td>
|
||||||
$maybe sharer <- msharer
|
$maybe sharer <- msharer
|
||||||
^{personLinkW sharer}
|
^{sharerLinkW sharer}
|
||||||
$nothing
|
$nothing
|
||||||
#{patchAuthorName patch}
|
#{patchAuthorName patch}
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
$# This file is part of Vervis.
|
|
||||||
$#
|
|
||||||
$# Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
|
||||||
$#
|
|
||||||
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
|
||||||
$#
|
|
||||||
$# The author(s) have dedicated all copyright and related and neighboring
|
|
||||||
$# rights to this software to the public domain worldwide. This software is
|
|
||||||
$# distributed without any warranty.
|
|
||||||
$#
|
|
||||||
$# You should have received a copy of the CC0 Public Domain Dedication along
|
|
||||||
$# with this software. If not, see
|
|
||||||
$# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
|
||||||
|
|
||||||
<a href=@{route $ sharerIdent sharer}>
|
|
||||||
$maybe name <- sharerName sharer
|
|
||||||
#{name}
|
|
||||||
$nothing
|
|
||||||
#{shr2text $ sharerIdent sharer}
|
|
|
@ -1,6 +1,6 @@
|
||||||
$# This file is part of Vervis.
|
$# This file is part of Vervis.
|
||||||
$#
|
$#
|
||||||
$# Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
$# Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
$#
|
$#
|
||||||
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
$#
|
$#
|
||||||
|
@ -22,6 +22,6 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
<td>
|
<td>
|
||||||
#{showDate $ ticketClaimRequestCreated tcr}
|
#{showDate $ ticketClaimRequestCreated tcr}
|
||||||
<td>
|
<td>
|
||||||
^{personLinkW sharer}
|
^{sharerLinkW sharer}
|
||||||
<td>
|
<td>
|
||||||
^{renderSourceT Markdown $ ticketClaimRequestMessage tcr}
|
^{renderSourceT Markdown $ ticketClaimRequestMessage tcr}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
$# This file is part of Vervis.
|
$# This file is part of Vervis.
|
||||||
$#
|
$#
|
||||||
$# Written in 2016, 2018 by fr33domlover <fr33domlover@riseup.net>.
|
$# Written in 2016, 2018, 2019 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
$#
|
$#
|
||||||
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
$#
|
$#
|
||||||
|
@ -20,12 +20,12 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
<th>Status
|
<th>Status
|
||||||
$if forward
|
$if forward
|
||||||
<th>Remove dependency
|
<th>Remove dependency
|
||||||
$forall (E.Value number, Entity _ author, E.Value title, E.Value status) <- rows
|
$forall (number, author, title, status) <- rows
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href=@{TicketR shr prj number}>#{number}
|
<a href=@{TicketR shr prj number}>#{number}
|
||||||
<td>
|
<td>
|
||||||
^{personLinkW author}
|
^{sharerLinkFedW author}
|
||||||
<td>
|
<td>
|
||||||
<a href=@{TicketR shr prj number}>#{title}
|
<a href=@{TicketR shr prj number}>#{title}
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
$# This file is part of Vervis.
|
$# This file is part of Vervis.
|
||||||
$#
|
$#
|
||||||
$# Written in 2016, 2018 by fr33domlover <fr33domlover@riseup.net>.
|
$# Written in 2016, 2018, 2019 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
$#
|
$#
|
||||||
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
$#
|
$#
|
||||||
|
@ -41,7 +41,7 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Created on #{showDate $ ticketCreated ticket} by
|
Created on #{showDate $ ticketCreated ticket} by
|
||||||
^{personLinkW author}
|
^{sharerLinkFedW author}
|
||||||
|
|
||||||
$if ticketStatus ticket /= TSClosed
|
$if ticketStatus ticket /= TSClosed
|
||||||
<p>
|
<p>
|
||||||
|
@ -51,7 +51,7 @@ $if ticketStatus ticket /= TSClosed
|
||||||
|
|
||||||
^{buttonW POST "Unclaim this ticket" (TicketUnclaimR shar proj num)}
|
^{buttonW POST "Unclaim this ticket" (TicketUnclaimR shar proj num)}
|
||||||
$else
|
$else
|
||||||
Assigned to ^{personLinkW assignee}.
|
Assigned to ^{sharerLinkW assignee}.
|
||||||
|
|
||||||
^{buttonW POST "Unassign this ticket" (TicketUnassignR shar proj num)}
|
^{buttonW POST "Unassign this ticket" (TicketUnassignR shar proj num)}
|
||||||
$nothing
|
$nothing
|
||||||
|
@ -90,8 +90,9 @@ $if ticketStatus ticket /= TSClosed
|
||||||
|
|
||||||
^{buttonW POST "Close this ticket" (TicketCloseR shar proj num)}
|
^{buttonW POST "Close this ticket" (TicketCloseR shar proj num)}
|
||||||
$of TSClosed
|
$of TSClosed
|
||||||
Closed on #{showDate $ ticketClosed ticket} by
|
Closed on #{showDate $ ticketClosed ticket}
|
||||||
^{personLinkW closer}.
|
$maybe closer <- mcloser
|
||||||
|
by ^{sharerLinkW closer}.
|
||||||
|
|
||||||
^{buttonW POST "Reopen this ticket" (TicketOpenR shar proj num)}
|
^{buttonW POST "Reopen this ticket" (TicketOpenR shar proj num)}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
$# This file is part of Vervis.
|
$# This file is part of Vervis.
|
||||||
$#
|
$#
|
||||||
$# Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
$# Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
$#
|
$#
|
||||||
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
$# ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
$#
|
$#
|
||||||
|
@ -30,7 +30,7 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
<span>
|
<span>
|
||||||
#{showDate $ tsCreatedAt ts}
|
#{showDate $ tsCreatedAt ts}
|
||||||
|
|
||||||
^{personLinkW $ tsCreatedBy ts}
|
^{sharerLinkFedW $ tsCreatedBy ts}
|
||||||
|
|
||||||
<a href=@{TicketR shr prj $ tsNumber ts}>
|
<a href=@{TicketR shr prj $ tsNumber ts}>
|
||||||
#{tsTitle ts}
|
#{tsTitle ts}
|
||||||
|
|
Loading…
Reference in a new issue