mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-04-05 19:09:32 +00:00
commit
03a67d672e
@ -9,6 +9,7 @@
|
|||||||
- New feature "Integration Endpoint". Allows an admin to upload files
|
- New feature "Integration Endpoint". Allows an admin to upload files
|
||||||
to any collective using a separate endpoint.
|
to any collective using a separate endpoint.
|
||||||
- New feature: add files to existing items.
|
- New feature: add files to existing items.
|
||||||
|
- New feature: reorder attachments via drag and drop.
|
||||||
- The document list on the front-page has been rewritten. The table is
|
- The document list on the front-page has been rewritten. The table is
|
||||||
removed and documents are now presented in a “card view”.
|
removed and documents are now presented in a “card view”.
|
||||||
- Amend the mail-to-pdf conversion to include the e-mail date.
|
- Amend the mail-to-pdf conversion to include the e-mail date.
|
||||||
@ -47,6 +48,8 @@ The joex and rest-server component have new config sections:
|
|||||||
- The data used in `/sec/collective/settings` was extended with a
|
- The data used in `/sec/collective/settings` was extended with a
|
||||||
boolean value to enable/disable the "integration endpoint" for a
|
boolean value to enable/disable the "integration endpoint" for a
|
||||||
collective.
|
collective.
|
||||||
|
- Add `/sec/item/{itemId}/attachment/movebefore` to move an attachment
|
||||||
|
before another.
|
||||||
|
|
||||||
|
|
||||||
## v0.5.0
|
## v0.5.0
|
||||||
|
1
elm.json
1
elm.json
@ -20,6 +20,7 @@
|
|||||||
"elm/url": "1.0.0",
|
"elm/url": "1.0.0",
|
||||||
"elm-explorations/markdown": "1.0.0",
|
"elm-explorations/markdown": "1.0.0",
|
||||||
"justinmimbs/date": "3.1.2",
|
"justinmimbs/date": "3.1.2",
|
||||||
|
"norpan/elm-html5-drag-drop": "3.1.4",
|
||||||
"ryannhg/date-format": "2.3.0",
|
"ryannhg/date-format": "2.3.0",
|
||||||
"truqu/elm-base64": "2.0.4"
|
"truqu/elm-base64": "2.0.4"
|
||||||
},
|
},
|
||||||
|
@ -75,6 +75,8 @@ trait OItem[F[_]] {
|
|||||||
def findByFileSource(checksum: String, sourceId: Ident): F[Vector[RItem]]
|
def findByFileSource(checksum: String, sourceId: Ident): F[Vector[RItem]]
|
||||||
|
|
||||||
def deleteAttachment(id: Ident, collective: Ident): F[Int]
|
def deleteAttachment(id: Ident, collective: Ident): F[Int]
|
||||||
|
|
||||||
|
def moveAttachmentBefore(itemId: Ident, source: Ident, target: Ident): F[AddResult]
|
||||||
}
|
}
|
||||||
|
|
||||||
object OItem {
|
object OItem {
|
||||||
@ -121,6 +123,16 @@ object OItem {
|
|||||||
def apply[F[_]: Effect](store: Store[F]): Resource[F, OItem[F]] =
|
def apply[F[_]: Effect](store: Store[F]): Resource[F, OItem[F]] =
|
||||||
Resource.pure[F, OItem[F]](new OItem[F] {
|
Resource.pure[F, OItem[F]](new OItem[F] {
|
||||||
|
|
||||||
|
def moveAttachmentBefore(
|
||||||
|
itemId: Ident,
|
||||||
|
source: Ident,
|
||||||
|
target: Ident
|
||||||
|
): F[AddResult] =
|
||||||
|
store
|
||||||
|
.transact(QItem.moveAttachmentBefore(itemId, source, target))
|
||||||
|
.attempt
|
||||||
|
.map(AddResult.fromUpdate)
|
||||||
|
|
||||||
def findItem(id: Ident, collective: Ident): F[Option[ItemData]] =
|
def findItem(id: Ident, collective: Ident): F[Option[ItemData]] =
|
||||||
store
|
store
|
||||||
.transact(QItem.findItem(id))
|
.transact(QItem.findItem(id))
|
||||||
|
@ -1239,6 +1239,30 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/ItemProposals"
|
$ref: "#/components/schemas/ItemProposals"
|
||||||
|
/sec/item/{itemId}/attachment/movebefore:
|
||||||
|
post:
|
||||||
|
tags: [ Item ]
|
||||||
|
summary: Reorder attachments within an item
|
||||||
|
description: |
|
||||||
|
Moves the `source` attachment before the `target` attachment,
|
||||||
|
such that `source` becomes the immediate neighbor of `target`
|
||||||
|
with a lower position.
|
||||||
|
security:
|
||||||
|
- authTokenHeader: []
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/itemId"
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/MoveAttachment"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicResult"
|
||||||
|
|
||||||
/sec/attachment/{id}:
|
/sec/attachment/{id}:
|
||||||
delete:
|
delete:
|
||||||
@ -1945,6 +1969,19 @@ paths:
|
|||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
|
MoveAttachment:
|
||||||
|
description: |
|
||||||
|
Data to move an attachment to another position.
|
||||||
|
required:
|
||||||
|
- source
|
||||||
|
- target
|
||||||
|
properties:
|
||||||
|
source:
|
||||||
|
type: string
|
||||||
|
format: ident
|
||||||
|
target:
|
||||||
|
type: string
|
||||||
|
format: ident
|
||||||
ScanMailboxSettingsList:
|
ScanMailboxSettingsList:
|
||||||
description: |
|
description: |
|
||||||
A list of scan-mailbox tasks.
|
A list of scan-mailbox tasks.
|
||||||
|
@ -137,6 +137,14 @@ object ItemRoutes {
|
|||||||
resp <- Ok(ip)
|
resp <- Ok(ip)
|
||||||
} yield resp
|
} yield resp
|
||||||
|
|
||||||
|
case req @ POST -> Root / Ident(id) / "attachment" / "movebefore" =>
|
||||||
|
for {
|
||||||
|
data <- req.as[MoveAttachment]
|
||||||
|
_ <- logger.fdebug(s"Move item (${id.id}) attachment $data")
|
||||||
|
res <- backend.item.moveAttachmentBefore(id, data.source, data.target)
|
||||||
|
resp <- Ok(Conversions.basicResult(res, "Attachment moved."))
|
||||||
|
} yield resp
|
||||||
|
|
||||||
case DELETE -> Root / Ident(id) =>
|
case DELETE -> Root / Ident(id) =>
|
||||||
for {
|
for {
|
||||||
n <- backend.item.deleteItem(id, user.account.collective)
|
n <- backend.item.deleteItem(id, user.account.collective)
|
||||||
|
@ -72,12 +72,18 @@ case class Column(name: String, ns: String = "", alias: String = "") {
|
|||||||
def isGt[A: Put](a: A): Fragment =
|
def isGt[A: Put](a: A): Fragment =
|
||||||
f ++ fr"> $a"
|
f ++ fr"> $a"
|
||||||
|
|
||||||
|
def isGte[A: Put](a: A): Fragment =
|
||||||
|
f ++ fr">= $a"
|
||||||
|
|
||||||
def isGt(c: Column): Fragment =
|
def isGt(c: Column): Fragment =
|
||||||
f ++ fr">" ++ c.f
|
f ++ fr">" ++ c.f
|
||||||
|
|
||||||
def isLt[A: Put](a: A): Fragment =
|
def isLt[A: Put](a: A): Fragment =
|
||||||
f ++ fr"< $a"
|
f ++ fr"< $a"
|
||||||
|
|
||||||
|
def isLte[A: Put](a: A): Fragment =
|
||||||
|
f ++ fr"<= $a"
|
||||||
|
|
||||||
def isLt(c: Column): Fragment =
|
def isLt(c: Column): Fragment =
|
||||||
f ++ fr"<" ++ c.f
|
f ++ fr"<" ++ c.f
|
||||||
|
|
||||||
@ -103,4 +109,10 @@ case class Column(name: String, ns: String = "", alias: String = "") {
|
|||||||
|
|
||||||
def max: Fragment =
|
def max: Fragment =
|
||||||
fr"MAX(" ++ f ++ fr")"
|
fr"MAX(" ++ f ++ fr")"
|
||||||
|
|
||||||
|
def increment[A: Put](a: A): Fragment =
|
||||||
|
f ++ fr"=" ++ f ++ fr"+ $a"
|
||||||
|
|
||||||
|
def decrement[A: Put](a: A): Fragment =
|
||||||
|
f ++ fr"=" ++ f ++ fr"- $a"
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package docspell.store.queries
|
package docspell.store.queries
|
||||||
|
|
||||||
import bitpeace.FileMeta
|
import bitpeace.FileMeta
|
||||||
import cats.implicits._
|
|
||||||
import cats.effect.Sync
|
import cats.effect.Sync
|
||||||
|
import cats.data.OptionT
|
||||||
|
import cats.implicits._
|
||||||
import fs2.Stream
|
import fs2.Stream
|
||||||
import doobie._
|
import doobie._
|
||||||
import doobie.implicits._
|
import doobie.implicits._
|
||||||
@ -16,6 +17,44 @@ import org.log4s._
|
|||||||
object QItem {
|
object QItem {
|
||||||
private[this] val logger = getLogger
|
private[this] val logger = getLogger
|
||||||
|
|
||||||
|
def moveAttachmentBefore(
|
||||||
|
itemId: Ident,
|
||||||
|
source: Ident,
|
||||||
|
target: Ident
|
||||||
|
): ConnectionIO[Int] = {
|
||||||
|
|
||||||
|
// rs < rt
|
||||||
|
def moveBack(rs: RAttachment, rt: RAttachment): ConnectionIO[Int] =
|
||||||
|
for {
|
||||||
|
n <- RAttachment.decPositions(itemId, rs.position, rt.position)
|
||||||
|
k <- RAttachment.updatePosition(rs.id, rt.position)
|
||||||
|
} yield n + k
|
||||||
|
|
||||||
|
// rs > rt
|
||||||
|
def moveForward(rs: RAttachment, rt: RAttachment): ConnectionIO[Int] =
|
||||||
|
for {
|
||||||
|
n <- RAttachment.incPositions(itemId, rt.position, rs.position)
|
||||||
|
k <- RAttachment.updatePosition(rs.id, rt.position)
|
||||||
|
} yield n + k
|
||||||
|
|
||||||
|
(for {
|
||||||
|
_ <- OptionT.liftF(
|
||||||
|
if (source == target)
|
||||||
|
Sync[ConnectionIO].raiseError(new Exception("Attachments are the same!"))
|
||||||
|
else ().pure[ConnectionIO]
|
||||||
|
)
|
||||||
|
rs <- OptionT(RAttachment.findById(source)).filter(_.itemId == itemId)
|
||||||
|
rt <- OptionT(RAttachment.findById(target)).filter(_.itemId == itemId)
|
||||||
|
n <- OptionT.liftF(
|
||||||
|
if (rs.position == rt.position || rs.position + 1 == rt.position)
|
||||||
|
0.pure[ConnectionIO]
|
||||||
|
else if (rs.position < rt.position) moveBack(rs, rt)
|
||||||
|
else moveForward(rs, rt)
|
||||||
|
)
|
||||||
|
} yield n).getOrElse(0)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
case class ItemData(
|
case class ItemData(
|
||||||
item: RItem,
|
item: RItem,
|
||||||
corrOrg: Option[ROrganization],
|
corrOrg: Option[ROrganization],
|
||||||
|
@ -38,6 +38,20 @@ object RAttachment {
|
|||||||
fr"${v.id},${v.itemId},${v.fileId.id},${v.position},${v.created},${v.name}"
|
fr"${v.id},${v.itemId},${v.fileId.id},${v.position},${v.created},${v.name}"
|
||||||
).update.run
|
).update.run
|
||||||
|
|
||||||
|
def decPositions(iId: Ident, lowerBound: Int, upperBound: Int): ConnectionIO[Int] =
|
||||||
|
updateRow(
|
||||||
|
table,
|
||||||
|
and(itemId.is(iId), position.isGte(lowerBound), position.isLte(upperBound)),
|
||||||
|
position.decrement(1)
|
||||||
|
).update.run
|
||||||
|
|
||||||
|
def incPositions(iId: Ident, lowerBound: Int, upperBound: Int): ConnectionIO[Int] =
|
||||||
|
updateRow(
|
||||||
|
table,
|
||||||
|
and(itemId.is(iId), position.isGte(lowerBound), position.isLte(upperBound)),
|
||||||
|
position.increment(1)
|
||||||
|
).update.run
|
||||||
|
|
||||||
def nextPosition(id: Ident): ConnectionIO[Int] =
|
def nextPosition(id: Ident): ConnectionIO[Int] =
|
||||||
for {
|
for {
|
||||||
max <- selectSimple(position.max, table, itemId.is(id)).query[Option[Int]].unique
|
max <- selectSimple(position.max, table, itemId.is(id)).query[Option[Int]].unique
|
||||||
|
@ -42,6 +42,7 @@ module Api exposing
|
|||||||
, login
|
, login
|
||||||
, loginSession
|
, loginSession
|
||||||
, logout
|
, logout
|
||||||
|
, moveAttachmentBefore
|
||||||
, newInvite
|
, newInvite
|
||||||
, postEquipment
|
, postEquipment
|
||||||
, postNewUser
|
, postNewUser
|
||||||
@ -100,6 +101,7 @@ import Api.Model.ItemProposals exposing (ItemProposals)
|
|||||||
import Api.Model.ItemSearch exposing (ItemSearch)
|
import Api.Model.ItemSearch exposing (ItemSearch)
|
||||||
import Api.Model.ItemUploadMeta exposing (ItemUploadMeta)
|
import Api.Model.ItemUploadMeta exposing (ItemUploadMeta)
|
||||||
import Api.Model.JobQueueState exposing (JobQueueState)
|
import Api.Model.JobQueueState exposing (JobQueueState)
|
||||||
|
import Api.Model.MoveAttachment exposing (MoveAttachment)
|
||||||
import Api.Model.NotificationSettings exposing (NotificationSettings)
|
import Api.Model.NotificationSettings exposing (NotificationSettings)
|
||||||
import Api.Model.OptionalDate exposing (OptionalDate)
|
import Api.Model.OptionalDate exposing (OptionalDate)
|
||||||
import Api.Model.OptionalId exposing (OptionalId)
|
import Api.Model.OptionalId exposing (OptionalId)
|
||||||
@ -1009,6 +1011,21 @@ getJobQueueStateTask flags =
|
|||||||
-- Item
|
-- Item
|
||||||
|
|
||||||
|
|
||||||
|
moveAttachmentBefore :
|
||||||
|
Flags
|
||||||
|
-> String
|
||||||
|
-> MoveAttachment
|
||||||
|
-> (Result Http.Error BasicResult -> msg)
|
||||||
|
-> Cmd msg
|
||||||
|
moveAttachmentBefore flags itemId data receive =
|
||||||
|
Http2.authPost
|
||||||
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/item/" ++ itemId ++ "/attachment/movebefore"
|
||||||
|
, account = getAccount flags
|
||||||
|
, body = Http.jsonBody (Api.Model.MoveAttachment.encode data)
|
||||||
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
itemSearch : Flags -> ItemSearch -> (Result Http.Error ItemLightList -> msg) -> Cmd msg
|
itemSearch : Flags -> ItemSearch -> (Result Http.Error ItemLightList -> msg) -> Cmd msg
|
||||||
itemSearch flags search receive =
|
itemSearch flags search receive =
|
||||||
Http2.authPost
|
Http2.authPost
|
||||||
|
@ -18,6 +18,7 @@ import Html exposing (..)
|
|||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
import Html.Events exposing (..)
|
import Html.Events exposing (..)
|
||||||
import Json.Decode as D
|
import Json.Decode as D
|
||||||
|
import Util.Html exposing (onDragEnter, onDragLeave, onDragOver, onDropFiles)
|
||||||
|
|
||||||
|
|
||||||
type alias State =
|
type alias State =
|
||||||
@ -111,10 +112,10 @@ view : Model -> Html Msg
|
|||||||
view model =
|
view model =
|
||||||
div
|
div
|
||||||
[ classList (model.settings.classList model.state)
|
[ classList (model.settings.classList model.state)
|
||||||
, hijackOn "dragenter" (D.succeed DragEnter)
|
, onDragEnter DragEnter
|
||||||
, hijackOn "dragover" (D.succeed DragEnter)
|
, onDragOver DragEnter
|
||||||
, hijackOn "dragleave" (D.succeed DragLeave)
|
, onDragLeave DragLeave
|
||||||
, hijackOn "drop" dropDecoder
|
, onDropFiles GotFiles
|
||||||
]
|
]
|
||||||
[ div [ class "ui icon header" ]
|
[ div [ class "ui icon header" ]
|
||||||
[ i [ class "mouse pointer icon" ] []
|
[ i [ class "mouse pointer icon" ] []
|
||||||
@ -156,18 +157,3 @@ filterMime settings files =
|
|||||||
|
|
||||||
else
|
else
|
||||||
List.filter pred files
|
List.filter pred files
|
||||||
|
|
||||||
|
|
||||||
dropDecoder : D.Decoder Msg
|
|
||||||
dropDecoder =
|
|
||||||
D.at [ "dataTransfer", "files" ] (D.oneOrMore GotFiles File.decoder)
|
|
||||||
|
|
||||||
|
|
||||||
hijackOn : String -> D.Decoder msg -> Attribute msg
|
|
||||||
hijackOn event decoder =
|
|
||||||
preventDefaultOn event (D.map hijack decoder)
|
|
||||||
|
|
||||||
|
|
||||||
hijack : msg -> ( msg, Bool )
|
|
||||||
hijack msg =
|
|
||||||
( msg, True )
|
|
||||||
|
@ -14,6 +14,7 @@ import Api.Model.EquipmentList exposing (EquipmentList)
|
|||||||
import Api.Model.IdName exposing (IdName)
|
import Api.Model.IdName exposing (IdName)
|
||||||
import Api.Model.ItemDetail exposing (ItemDetail)
|
import Api.Model.ItemDetail exposing (ItemDetail)
|
||||||
import Api.Model.ItemProposals exposing (ItemProposals)
|
import Api.Model.ItemProposals exposing (ItemProposals)
|
||||||
|
import Api.Model.MoveAttachment exposing (MoveAttachment)
|
||||||
import Api.Model.OptionalDate exposing (OptionalDate)
|
import Api.Model.OptionalDate exposing (OptionalDate)
|
||||||
import Api.Model.OptionalId exposing (OptionalId)
|
import Api.Model.OptionalId exposing (OptionalId)
|
||||||
import Api.Model.OptionalText exposing (OptionalText)
|
import Api.Model.OptionalText exposing (OptionalText)
|
||||||
@ -39,6 +40,7 @@ import File exposing (File)
|
|||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
import Html.Events exposing (onCheck, onClick, onInput)
|
import Html.Events exposing (onCheck, onClick, onInput)
|
||||||
|
import Html5.DragDrop as DD
|
||||||
import Http
|
import Http
|
||||||
import Markdown
|
import Markdown
|
||||||
import Page exposing (Page(..))
|
import Page exposing (Page(..))
|
||||||
@ -88,6 +90,7 @@ type alias Model =
|
|||||||
, completed : Set String
|
, completed : Set String
|
||||||
, errored : Set String
|
, errored : Set String
|
||||||
, loading : Set String
|
, loading : Set String
|
||||||
|
, attachDD : DD.Model String String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -182,6 +185,7 @@ emptyModel =
|
|||||||
, completed = Set.empty
|
, completed = Set.empty
|
||||||
, errored = Set.empty
|
, errored = Set.empty
|
||||||
, loading = Set.empty
|
, loading = Set.empty
|
||||||
|
, attachDD = DD.init
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -244,6 +248,7 @@ type Msg
|
|||||||
| AddFilesUploadResp String (Result Http.Error BasicResult)
|
| AddFilesUploadResp String (Result Http.Error BasicResult)
|
||||||
| AddFilesProgress String Http.Progress
|
| AddFilesProgress String Http.Progress
|
||||||
| AddFilesReset
|
| AddFilesReset
|
||||||
|
| AttachDDMsg (DD.Msg String String)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1174,6 +1179,28 @@ update key flags next msg model =
|
|||||||
in
|
in
|
||||||
noSub ( model, updateBars )
|
noSub ( model, updateBars )
|
||||||
|
|
||||||
|
AttachDDMsg lm ->
|
||||||
|
let
|
||||||
|
( model_, result ) =
|
||||||
|
DD.update lm model.attachDD
|
||||||
|
|
||||||
|
cmd =
|
||||||
|
case result of
|
||||||
|
Just ( src, trg, _ ) ->
|
||||||
|
if src /= trg then
|
||||||
|
Api.moveAttachmentBefore flags
|
||||||
|
model.item.id
|
||||||
|
(MoveAttachment src trg)
|
||||||
|
SaveResp
|
||||||
|
|
||||||
|
else
|
||||||
|
Cmd.none
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
Cmd.none
|
||||||
|
in
|
||||||
|
noSub ( { model | attachDD = model_ }, cmd )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- view
|
-- view
|
||||||
@ -1419,20 +1446,38 @@ renderAttachmentsTabMenu model =
|
|||||||
[ text "E-Mails"
|
[ text "E-Mails"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
highlight el =
|
||||||
|
let
|
||||||
|
dropId =
|
||||||
|
DD.getDropId model.attachDD
|
||||||
|
|
||||||
|
dragId =
|
||||||
|
DD.getDragId model.attachDD
|
||||||
|
|
||||||
|
enable =
|
||||||
|
Just el.id == dropId && dropId /= dragId
|
||||||
|
in
|
||||||
|
[ ( "current-drop-target", enable )
|
||||||
|
]
|
||||||
in
|
in
|
||||||
div [ class "ui top attached tabular menu" ]
|
div [ class "ui top attached tabular menu" ]
|
||||||
(List.indexedMap
|
(List.indexedMap
|
||||||
(\pos ->
|
(\pos ->
|
||||||
\el ->
|
\el ->
|
||||||
a
|
a
|
||||||
[ classList
|
([ classList <|
|
||||||
[ ( "item", True )
|
[ ( "item", True )
|
||||||
, ( "active", attachmentVisible model pos )
|
, ( "active", attachmentVisible model pos )
|
||||||
]
|
]
|
||||||
, title (Maybe.withDefault "No Name" el.name)
|
++ highlight el
|
||||||
, href ""
|
, title (Maybe.withDefault "No Name" el.name)
|
||||||
, onClick (SetActiveAttachment pos)
|
, href ""
|
||||||
]
|
, onClick (SetActiveAttachment pos)
|
||||||
|
]
|
||||||
|
++ DD.draggable AttachDDMsg el.id
|
||||||
|
++ DD.droppable AttachDDMsg el.id
|
||||||
|
)
|
||||||
[ Maybe.map (Util.String.ellipsis 20) el.name
|
[ Maybe.map (Util.String.ellipsis 20) el.name
|
||||||
|> Maybe.withDefault "No Name"
|
|> Maybe.withDefault "No Name"
|
||||||
|> text
|
|> text
|
||||||
|
@ -4,13 +4,18 @@ module Util.Html exposing
|
|||||||
, classActive
|
, classActive
|
||||||
, intToKeyCode
|
, intToKeyCode
|
||||||
, onClickk
|
, onClickk
|
||||||
|
, onDragEnter
|
||||||
|
, onDragLeave
|
||||||
|
, onDragOver
|
||||||
|
, onDropFiles
|
||||||
, onKeyUp
|
, onKeyUp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import File exposing (File)
|
||||||
import Html exposing (Attribute, Html, i)
|
import Html exposing (Attribute, Html, i)
|
||||||
import Html.Attributes exposing (class)
|
import Html.Attributes exposing (class)
|
||||||
import Html.Events exposing (keyCode, on)
|
import Html.Events exposing (keyCode, on, preventDefaultOn)
|
||||||
import Json.Decode as Decode
|
import Json.Decode as D
|
||||||
|
|
||||||
|
|
||||||
checkboxChecked : Html msg
|
checkboxChecked : Html msg
|
||||||
@ -68,12 +73,12 @@ intToKeyCode code =
|
|||||||
|
|
||||||
onKeyUp : (Int -> msg) -> Attribute msg
|
onKeyUp : (Int -> msg) -> Attribute msg
|
||||||
onKeyUp tagger =
|
onKeyUp tagger =
|
||||||
on "keyup" (Decode.map tagger keyCode)
|
on "keyup" (D.map tagger keyCode)
|
||||||
|
|
||||||
|
|
||||||
onClickk : msg -> Attribute msg
|
onClickk : msg -> Attribute msg
|
||||||
onClickk msg =
|
onClickk msg =
|
||||||
Html.Events.preventDefaultOn "click" (Decode.map alwaysPreventDefault (Decode.succeed msg))
|
Html.Events.preventDefaultOn "click" (D.map alwaysPreventDefault (D.succeed msg))
|
||||||
|
|
||||||
|
|
||||||
alwaysPreventDefault : msg -> ( msg, Bool )
|
alwaysPreventDefault : msg -> ( msg, Bool )
|
||||||
@ -92,3 +97,42 @@ classActive active classes =
|
|||||||
""
|
""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
onDragEnter : msg -> Attribute msg
|
||||||
|
onDragEnter m =
|
||||||
|
hijackOn "dragenter" (D.succeed m)
|
||||||
|
|
||||||
|
|
||||||
|
onDragOver : msg -> Attribute msg
|
||||||
|
onDragOver m =
|
||||||
|
hijackOn "dragover" (D.succeed m)
|
||||||
|
|
||||||
|
|
||||||
|
onDragLeave : msg -> Attribute msg
|
||||||
|
onDragLeave m =
|
||||||
|
hijackOn "dragleave" (D.succeed m)
|
||||||
|
|
||||||
|
|
||||||
|
onDrop : msg -> Attribute msg
|
||||||
|
onDrop m =
|
||||||
|
hijackOn "drop" (D.succeed m)
|
||||||
|
|
||||||
|
|
||||||
|
onDropFiles : (File -> List File -> msg) -> Attribute msg
|
||||||
|
onDropFiles tagger =
|
||||||
|
let
|
||||||
|
dropFilesDecoder =
|
||||||
|
D.at [ "dataTransfer", "files" ] (D.oneOrMore tagger File.decoder)
|
||||||
|
in
|
||||||
|
hijackOn "drop" dropFilesDecoder
|
||||||
|
|
||||||
|
|
||||||
|
hijackOn : String -> D.Decoder msg -> Attribute msg
|
||||||
|
hijackOn event decoder =
|
||||||
|
preventDefaultOn event (D.map hijack decoder)
|
||||||
|
|
||||||
|
|
||||||
|
hijack : msg -> ( msg, Bool )
|
||||||
|
hijack msg =
|
||||||
|
( msg, True )
|
||||||
|
@ -143,6 +143,10 @@ textarea.markdown-editor {
|
|||||||
background: rgba(240,248,255,0.4);
|
background: rgba(240,248,255,0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.default-layout .ui.menu .item.current-drop-target {
|
||||||
|
background: rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
label span.muted {
|
label span.muted {
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
color: rgba(0,0,0,0.6);
|
color: rgba(0,0,0,0.6);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user