diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OCollective.scala b/modules/backend/src/main/scala/docspell/backend/ops/OCollective.scala index 9a598726..14377f35 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OCollective.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OCollective.scala @@ -62,6 +62,8 @@ trait OCollective[F[_]] { def startLearnClassifier(collective: Ident): F[Unit] + def startEmptyTrash(collective: Ident): F[Unit] + /** Submits a task that (re)generates the preview images for all * attachments of the given collective. */ @@ -147,9 +149,14 @@ object OCollective { .transact(RCollective.updateSettings(collective, sett)) .attempt .map(AddResult.fromUpdate) - .flatMap(res => updateLearnClassifierTask(collective, sett) *> res.pure[F]) + .flatMap(res => + updateLearnClassifierTask(collective, sett) *> updateEmptyTrashTask( + collective, + sett + ) *> res.pure[F] + ) - def updateLearnClassifierTask(coll: Ident, sett: Settings) = + private def updateLearnClassifierTask(coll: Ident, sett: Settings): F[Unit] = for { id <- Ident.randomId[F] on = sett.classifier.map(_.enabled).getOrElse(false) @@ -166,6 +173,22 @@ object OCollective { _ <- joex.notifyAllNodes } yield () + private def updateEmptyTrashTask(coll: Ident, sett: Settings): F[Unit] = + for { + id <- Ident.randomId[F] + timer = sett.emptyTrash.getOrElse(CalEvent.unsafe("")) + ut = UserTask( + id, + EmptyTrashArgs.taskName, + true, + timer, + None, + EmptyTrashArgs(coll) + ) + _ <- uts.updateOneTask(AccountId(coll, EmptyTrashArgs.taskName), ut) + _ <- joex.notifyAllNodes + } yield () + def startLearnClassifier(collective: Ident): F[Unit] = for { id <- Ident.randomId[F] @@ -182,6 +205,22 @@ object OCollective { _ <- joex.notifyAllNodes } yield () + def startEmptyTrash(collective: Ident): F[Unit] = + for { + id <- Ident.randomId[F] + ut <- UserTask( + id, + EmptyTrashArgs.taskName, + true, + CalEvent(WeekdayComponent.All, DateEvent.All, TimeEvent.All), + None, + EmptyTrashArgs(collective) + ).encode.toPeriodicTask(AccountId(collective, EmptyTrashArgs.taskName)) + job <- ut.toJob + _ <- queue.insert(job) + _ <- joex.notifyAllNodes + } yield () + def findSettings(collective: Ident): F[Option[OCollective.Settings]] = store.transact(RCollective.getSettings(collective)) diff --git a/modules/common/src/main/scala/docspell/common/EmptyTrashArgs.scala b/modules/common/src/main/scala/docspell/common/EmptyTrashArgs.scala new file mode 100644 index 00000000..2c85fddf --- /dev/null +++ b/modules/common/src/main/scala/docspell/common/EmptyTrashArgs.scala @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Docspell Contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package docspell.common + +import docspell.common.syntax.all._ + +import io.circe._ +import io.circe.generic.semiauto._ + +/** Arguments to the empty-trash task. + * + * This task is run periodically to really delete all soft-deleted + * items. These are items with state `ItemState.Deleted`. + */ +case class EmptyTrashArgs( + collective: Ident +) { + + def makeSubject: String = + "Empty trash " + +} + +object EmptyTrashArgs { + + val taskName = Ident.unsafe("empty-trash") + + implicit val jsonEncoder: Encoder[EmptyTrashArgs] = + deriveEncoder[EmptyTrashArgs] + implicit val jsonDecoder: Decoder[EmptyTrashArgs] = + deriveDecoder[EmptyTrashArgs] + + def parse(str: String): Either[Throwable, EmptyTrashArgs] = + str.parseJsonAs[EmptyTrashArgs] + +} diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index 2256ae44..7c43a369 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -1136,6 +1136,27 @@ paths: schema: $ref: "#/components/schemas/BasicResult" + /sec/collective/emptytrash/startonce: + post: + operationId: "sec-collective-emptytrash-start-now" + tags: [ Collective ] + summary: Starts the empty trash task + description: | + Submits a task to remove all items from the database that have + been "soft-deleted". This task is also run periodically and + can be triggered here to be immediatly submitted. + + The request is empty, settings are used from the collective. + security: + - authTokenHeader: [] + responses: + 200: + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/BasicResult" + /sec/user: get: operationId: "sec-user-get-all" @@ -5246,6 +5267,7 @@ components: - language - integrationEnabled - classifier + - emptyTrashSchedule properties: language: type: string @@ -5255,6 +5277,9 @@ components: description: | Whether the collective has the integration endpoint enabled. + emptyTrashSchedule: + type: string + format: calevent classifier: $ref: "#/components/schemas/ClassifierSetting" diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/CollectiveRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/CollectiveRoutes.scala index 3cbb27cd..bc7c3ef0 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/CollectiveRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/CollectiveRoutes.scala @@ -55,7 +55,8 @@ object CollectiveRoutes { settings.classifier.categoryList, settings.classifier.listType ) - ) + ), + Some(settings.emptyTrashSchedule) ) res <- backend.collective @@ -70,6 +71,7 @@ object CollectiveRoutes { CollectiveSettings( c.language, c.integrationEnabled, + c.emptyTrash.getOrElse(CalEvent.unsafe("*-*-1/7 03:00:00")), ClassifierSetting( c.classifier.map(_.itemCount).getOrElse(0), c.classifier @@ -101,6 +103,12 @@ object CollectiveRoutes { resp <- Ok(BasicResult(true, "Task submitted")) } yield resp + case POST -> Root / "emptytrash" / "startonce" => + for { + _ <- backend.collective.startEmptyTrash(user.account.collective) + resp <- Ok(BasicResult(true, "Task submitted")) + } yield resp + case GET -> Root => for { collDb <- backend.collective.find(user.account.collective) diff --git a/modules/store/src/main/resources/db/migration/h2/V1.25__add_empty_trash.sql b/modules/store/src/main/resources/db/migration/h2/V1.25__add_empty_trash.sql new file mode 100644 index 00000000..45650e03 --- /dev/null +++ b/modules/store/src/main/resources/db/migration/h2/V1.25__add_empty_trash.sql @@ -0,0 +1,6 @@ +CREATE TABLE "empty_trash_setting" ( + "cid" varchar(254) not null primary key, + "schedule" varchar(254) not null, + "created" timestamp not null, + foreign key ("cid") references "collective"("cid") +); diff --git a/modules/store/src/main/resources/db/migration/mariadb/V1.25.0__add_empty_trash.sql b/modules/store/src/main/resources/db/migration/mariadb/V1.25.0__add_empty_trash.sql new file mode 100644 index 00000000..d845e617 --- /dev/null +++ b/modules/store/src/main/resources/db/migration/mariadb/V1.25.0__add_empty_trash.sql @@ -0,0 +1,6 @@ +CREATE TABLE `empty_trash_setting` ( + `cid` varchar(254) not null primary key, + `schedule` varchar(254) not null, + `created` timestamp not null, + foreign key (`cid`) references `collective`(`cid`) +); diff --git a/modules/store/src/main/resources/db/migration/postgresql/V1.25.0__add_empty_trash.sql b/modules/store/src/main/resources/db/migration/postgresql/V1.25.0__add_empty_trash.sql new file mode 100644 index 00000000..45650e03 --- /dev/null +++ b/modules/store/src/main/resources/db/migration/postgresql/V1.25.0__add_empty_trash.sql @@ -0,0 +1,6 @@ +CREATE TABLE "empty_trash_setting" ( + "cid" varchar(254) not null primary key, + "schedule" varchar(254) not null, + "created" timestamp not null, + foreign key ("cid") references "collective"("cid") +); diff --git a/modules/store/src/main/scala/docspell/store/records/RCollective.scala b/modules/store/src/main/scala/docspell/store/records/RCollective.scala index bac88ccd..c3326b3d 100644 --- a/modules/store/src/main/scala/docspell/store/records/RCollective.scala +++ b/modules/store/src/main/scala/docspell/store/records/RCollective.scala @@ -13,6 +13,7 @@ import docspell.common._ import docspell.store.qb.DSL._ import docspell.store.qb._ +import com.github.eikek.calev._ import doobie._ import doobie.implicits._ @@ -73,17 +74,21 @@ object RCollective { T.integration.setTo(settings.integrationEnabled) ) ) - cls <- - Timestamp - .current[ConnectionIO] - .map(now => settings.classifier.map(_.toRecord(cid, now))) + now <- Timestamp.current[ConnectionIO] + cls = settings.classifier.map(_.toRecord(cid, now)) n2 <- cls match { case Some(cr) => RClassifierSetting.update(cr) case None => RClassifierSetting.delete(cid) } - } yield n1 + n2 + n3 <- settings.emptyTrash match { + case Some(trashSchedule) => + REmptyTrashSetting.update(REmptyTrashSetting(cid, trashSchedule, now)) + case None => + REmptyTrashSetting.delete(cid) + } + } yield n1 + n2 + n3 // this hides categories that have been deleted in the meantime // they are finally removed from the json array once the learn classifier task is run @@ -99,6 +104,7 @@ object RCollective { import RClassifierSetting.stringListMeta val c = RCollective.as("c") val cs = RClassifierSetting.as("cs") + val es = REmptyTrashSetting.as("es") Select( select( @@ -107,9 +113,10 @@ object RCollective { cs.schedule.s, cs.itemCount.s, cs.categories.s, - cs.listType.s + cs.listType.s, + es.schedule.s ), - from(c).leftJoin(cs, cs.cid === c.id), + from(c).leftJoin(cs, cs.cid === c.id).leftJoin(es, es.cid === c.id), c.id === coll ).build.query[Settings].option } @@ -160,7 +167,8 @@ object RCollective { case class Settings( language: Language, integrationEnabled: Boolean, - classifier: Option[RClassifierSetting.Classifier] + classifier: Option[RClassifierSetting.Classifier], + emptyTrash: Option[CalEvent] ) } diff --git a/modules/store/src/main/scala/docspell/store/records/REmptyTrashSetting.scala b/modules/store/src/main/scala/docspell/store/records/REmptyTrashSetting.scala new file mode 100644 index 00000000..f4df6900 --- /dev/null +++ b/modules/store/src/main/scala/docspell/store/records/REmptyTrashSetting.scala @@ -0,0 +1,68 @@ +/* + * Copyright 2020 Docspell Contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package docspell.store.records + +import cats.data.NonEmptyList +import cats.implicits._ + +import docspell.common._ +import docspell.store.qb.DSL._ +import docspell.store.qb._ + +import com.github.eikek.calev._ +import doobie._ +import doobie.implicits._ + +final case class REmptyTrashSetting( + cid: Ident, + schedule: CalEvent, + created: Timestamp +) + +object REmptyTrashSetting { + + final case class Table(alias: Option[String]) extends TableDef { + val tableName = "empty_trash_setting" + + val cid = Column[Ident]("cid", this) + val schedule = Column[CalEvent]("schedule", this) + val created = Column[Timestamp]("created", this) + val all = NonEmptyList.of[Column[_]](cid, schedule, created) + } + + val T = Table(None) + def as(alias: String): Table = + Table(Some(alias)) + + def insert(v: REmptyTrashSetting): ConnectionIO[Int] = + DML.insert( + T, + T.all, + fr"${v.cid},${v.schedule},${v.created}" + ) + + def update(v: REmptyTrashSetting): ConnectionIO[Int] = + for { + n1 <- DML.update( + T, + T.cid === v.cid, + DML.set( + T.schedule.setTo(v.schedule) + ) + ) + n2 <- if (n1 <= 0) insert(v) else 0.pure[ConnectionIO] + } yield n1 + n2 + + def findById(id: Ident): ConnectionIO[Option[REmptyTrashSetting]] = { + val sql = run(select(T.all), from(T), T.cid === id) + sql.query[REmptyTrashSetting].option + } + + def delete(coll: Ident): ConnectionIO[Int] = + DML.delete(T, T.cid === coll) + +} diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index 1ea82fbe..1f26e605 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -130,6 +130,7 @@ module Api exposing , setTagsMultiple , setUnconfirmed , startClassifier + , startEmptyTrash , startOnceNotifyDueItems , startOnceScanMailbox , startReIndex @@ -996,6 +997,19 @@ startClassifier flags receive = } +startEmptyTrash : + Flags + -> (Result Http.Error BasicResult -> msg) + -> Cmd msg +startEmptyTrash flags receive = + Http2.authPost + { url = flags.config.baseUrl ++ "/api/v1/sec/collective/emptytrash/startonce" + , account = getAccount flags + , body = Http.emptyBody + , expect = Http.expectJson receive Api.Model.BasicResult.decoder + } + + getTagCloud : Flags -> (Result Http.Error TagCloud -> msg) -> Cmd msg getTagCloud flags receive = Http2.authGet diff --git a/modules/webapp/src/main/elm/Comp/CollectiveSettingsForm.elm b/modules/webapp/src/main/elm/Comp/CollectiveSettingsForm.elm index 4d24d817..91da0d84 100644 --- a/modules/webapp/src/main/elm/Comp/CollectiveSettingsForm.elm +++ b/modules/webapp/src/main/elm/Comp/CollectiveSettingsForm.elm @@ -20,7 +20,9 @@ import Api.Model.CollectiveSettings exposing (CollectiveSettings) import Comp.Basic as B import Comp.ClassifierSettingsForm import Comp.Dropdown +import Comp.EmptyTrashForm import Comp.MenuBar as MB +import Data.CalEvent import Data.DropdownStyle as DS import Data.Flags exposing (Flags) import Data.Language exposing (Language) @@ -41,6 +43,8 @@ type alias Model = , fullTextReIndexResult : FulltextReindexResult , classifierModel : Comp.ClassifierSettingsForm.Model , startClassifierResult : ClassifierResult + , emptyTrashModel : Comp.EmptyTrashForm.Model + , startEmptyTrashResult : EmptyTrashResult } @@ -50,6 +54,11 @@ type ClassifierResult | ClassifierResultSubmitError String | ClassifierResultOk +type EmptyTrashResult + = EmptyTrashResultInitial + | EmptyTrashResultHttpError Http.Error + | EmptyTrashResultSubmitError String + | EmptyTrashResultOk type FulltextReindexResult = FulltextReindexInitial @@ -68,6 +77,9 @@ init flags settings = ( cm, cc ) = Comp.ClassifierSettingsForm.init flags settings.classifier + + ( em, ec ) = + Comp.EmptyTrashForm.init flags settings.emptyTrashSchedule in ( { langModel = Comp.Dropdown.makeSingleList @@ -80,8 +92,10 @@ init flags settings = , fullTextReIndexResult = FulltextReindexInitial , classifierModel = cm , startClassifierResult = ClassifierResultInitial + , emptyTrashModel = em + , startEmptyTrashResult = EmptyTrashResultInitial } - , Cmd.map ClassifierSettingMsg cc + , Cmd.batch [ Cmd.map ClassifierSettingMsg cc, Cmd.map EmptyTrashMsg ec ] ) @@ -96,6 +110,10 @@ getSettings model = |> Maybe.withDefault model.initSettings.language , integrationEnabled = model.intEnabled , classifier = cls + , emptyTrashSchedule = + Comp.EmptyTrashForm.getSettings model.emptyTrashModel + |> Maybe.withDefault Data.CalEvent.everyMonth + |> Data.CalEvent.makeEvent } ) (Comp.ClassifierSettingsForm.getSettings @@ -110,9 +128,12 @@ type Msg | TriggerReIndex | TriggerReIndexResult (Result Http.Error BasicResult) | ClassifierSettingMsg Comp.ClassifierSettingsForm.Msg + | EmptyTrashMsg Comp.EmptyTrashForm.Msg | SaveSettings | StartClassifierTask + | StartEmptyTrashTask | StartClassifierResp (Result Http.Error BasicResult) + | StartEmptyTrashResp (Result Http.Error BasicResult) update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe CollectiveSettings ) @@ -188,6 +209,18 @@ update flags msg model = , Nothing ) + EmptyTrashMsg lmsg -> + let + ( cm, cc ) = + Comp.EmptyTrashForm.update flags lmsg model.emptyTrashModel + in + ( { model + | emptyTrashModel = cm + } + , Cmd.map EmptyTrashMsg cc + , Nothing + ) + SaveSettings -> case getSettings model of Just s -> @@ -199,6 +232,10 @@ update flags msg model = StartClassifierTask -> ( model, Api.startClassifier flags StartClassifierResp, Nothing ) + StartEmptyTrashTask -> + ( model, Api.startEmptyTrash flags StartEmptyTrashResp, Nothing ) + + StartClassifierResp (Ok br) -> ( { model | startClassifierResult = @@ -218,6 +255,24 @@ update flags msg model = , Nothing ) + StartEmptyTrashResp (Ok br) -> + ( { model + | startEmptyTrashResult = + if br.success then + EmptyTrashResultOk + + else + EmptyTrashResultSubmitError br.message + } + , Cmd.none + , Nothing + ) + + StartEmptyTrashResp (Err err) -> + ( { model | startEmptyTrashResult = EmptyTrashResultHttpError err } + , Cmd.none + , Nothing + ) --- View2 @@ -257,7 +312,7 @@ view2 flags texts settings model = , end = [] , rootClasses = "mb-4" } - , h3 [ class S.header3 ] + , h2 [ class S.header2 ] [ text texts.documentLanguage ] , div [ class "mb-4" ] @@ -279,8 +334,8 @@ view2 flags texts settings model = [ ( "hidden", not flags.config.integrationEnabled ) ] ] - [ h3 - [ class S.header3 + [ h2 + [ class S.header2 ] [ text texts.integrationEndpoint ] @@ -311,8 +366,8 @@ view2 flags texts settings model = [ ( "hidden", not flags.config.fullTextSearchEnabled ) ] ] - [ h3 - [ class S.header3 ] + [ h2 + [ class S.header2 ] [ text texts.fulltextSearch ] , div [ class "mb-4" ] @@ -348,8 +403,8 @@ view2 flags texts settings model = [ ( " hidden", not flags.config.showClassificationSettings ) ] ] - [ h3 - [ class S.header3 ] + [ h2 + [ class S.header2 ] [ text texts.autoTagging ] , div @@ -371,6 +426,28 @@ view2 flags texts settings model = ] ] ] + , div [] + [ h2 [ class S.header2 ] + [ text texts.emptyTrash + ] + , div [ class "mb-4" ] + [ Html.map EmptyTrashMsg + (Comp.EmptyTrashForm.view texts.emptyTrashForm + settings + model.emptyTrashModel + ) + , div [ class "flex flex-row justify-end" ] + [ B.secondaryBasicButton + { handler = onClick StartEmptyTrashTask + , icon = "fa fa-play" + , label = texts.startNow + , disabled = model.emptyTrashModel.schedule == Nothing + , attrs = [ href "#" ] + } + , renderEmptyTrashResultMessage texts model.startEmptyTrashResult + ] + ] + ] ] @@ -427,3 +504,38 @@ renderFulltextReindexResultMessage texts result = FulltextReindexSubmitError m -> text m + +renderEmptyTrashResultMessage : Texts -> EmptyTrashResult -> Html msg +renderEmptyTrashResultMessage texts result = + let + isSuccess = + case result of + EmptyTrashResultOk -> + True + + _ -> + False + + isError = + not isSuccess + in + div + [ classList + [ ( S.errorMessage, isError ) + , ( S.successMessage, isSuccess ) + , ( "hidden", result == EmptyTrashResultInitial ) + ] + ] + [ case result of + EmptyTrashResultInitial -> + text "" + + EmptyTrashResultOk -> + text texts.emptyTrashTaskStarted + + EmptyTrashResultHttpError err -> + text (texts.httpError err) + + EmptyTrashResultSubmitError m -> + text m + ] diff --git a/modules/webapp/src/main/elm/Comp/EmptyTrashForm.elm b/modules/webapp/src/main/elm/Comp/EmptyTrashForm.elm new file mode 100644 index 00000000..86cfd1ab --- /dev/null +++ b/modules/webapp/src/main/elm/Comp/EmptyTrashForm.elm @@ -0,0 +1,106 @@ +{- + Copyright 2020 Docspell Contributors + + SPDX-License-Identifier: GPL-3.0-or-later +-} + + +module Comp.EmptyTrashForm exposing + ( Model + , Msg + , getSettings + , init + , update + , view + ) + +import Api +import Comp.CalEventInput +import Comp.Dropdown +import Comp.FixedDropdown +import Comp.IntField +import Data.CalEvent exposing (CalEvent) +import Data.DropdownStyle as DS +import Data.Flags exposing (Flags) +import Data.ListType exposing (ListType) +import Data.UiSettings exposing (UiSettings) +import Html exposing (..) +import Html.Attributes exposing (..) +import Http +import Markdown +import Messages.Comp.EmptyTrashForm exposing (Texts) +import Styles as S +import Util.Tag + + +type alias Model = + { scheduleModel : Comp.CalEventInput.Model + , schedule : Maybe CalEvent + } + + +type Msg + = ScheduleMsg Comp.CalEventInput.Msg + + +init : Flags -> String -> ( Model, Cmd Msg ) +init flags schedule = + let + newSchedule = + Data.CalEvent.fromEvent schedule + |> Maybe.withDefault Data.CalEvent.everyMonth + + ( cem, cec ) = + Comp.CalEventInput.init flags newSchedule + in + ( { scheduleModel = cem + , schedule = Just newSchedule + } + , Cmd.map ScheduleMsg cec + ) + + +getSettings : Model -> Maybe CalEvent +getSettings model = + model.schedule + + +update : Flags -> Msg -> Model -> ( Model, Cmd Msg ) +update flags msg model = + case msg of + ScheduleMsg lmsg -> + let + ( cm, cc, ce ) = + Comp.CalEventInput.update + flags + model.schedule + lmsg + model.scheduleModel + in + ( { model + | scheduleModel = cm + , schedule = ce + } + , Cmd.map ScheduleMsg cc + ) + + + +--- View2 + + +view : Texts -> UiSettings -> Model -> Html Msg +view texts _ model = + div [] + [ div [ class "mb-4" ] + [ label [ class S.inputLabel ] + [ text texts.schedule ] + , Html.map ScheduleMsg + (Comp.CalEventInput.view2 + texts.calEventInput + "" + model.schedule + model.scheduleModel + ) + ] + ] diff --git a/modules/webapp/src/main/elm/Messages/Comp/CollectiveSettingsForm.elm b/modules/webapp/src/main/elm/Messages/Comp/CollectiveSettingsForm.elm index d4bd8274..f86f1d94 100644 --- a/modules/webapp/src/main/elm/Messages/Comp/CollectiveSettingsForm.elm +++ b/modules/webapp/src/main/elm/Messages/Comp/CollectiveSettingsForm.elm @@ -15,6 +15,7 @@ import Data.Language exposing (Language) import Http import Messages.Basics import Messages.Comp.ClassifierSettingsForm +import Messages.Comp.EmptyTrashForm import Messages.Comp.HttpError import Messages.Data.Language @@ -22,6 +23,7 @@ import Messages.Data.Language type alias Texts = { basics : Messages.Basics.Texts , classifierSettingsForm : Messages.Comp.ClassifierSettingsForm.Texts + , emptyTrashForm : Messages.Comp.EmptyTrashForm.Texts , httpError : Http.Error -> String , save : String , saveSettings : String @@ -37,8 +39,10 @@ type alias Texts = , startNow : String , languageLabel : Language -> String , classifierTaskStarted : String + , emptyTrashTaskStarted : String , fulltextReindexSubmitted : String , fulltextReindexOkMissing : String + , emptyTrash : String } @@ -46,6 +50,7 @@ gb : Texts gb = { basics = Messages.Basics.gb , classifierSettingsForm = Messages.Comp.ClassifierSettingsForm.gb + , emptyTrashForm = Messages.Comp.EmptyTrashForm.gb , httpError = Messages.Comp.HttpError.gb , save = "Save" , saveSettings = "Save Settings" @@ -65,9 +70,11 @@ gb = , startNow = "Start now" , languageLabel = Messages.Data.Language.gb , classifierTaskStarted = "Classifier task started." + , emptyTrashTaskStarted = "Empty trash task started." , fulltextReindexSubmitted = "Fulltext Re-Index started." , fulltextReindexOkMissing = "Please type OK in the field if you really want to start re-indexing your data." + , emptyTrash = "Empty Trash" } @@ -75,6 +82,7 @@ de : Texts de = { basics = Messages.Basics.de , classifierSettingsForm = Messages.Comp.ClassifierSettingsForm.de + , emptyTrashForm = Messages.Comp.EmptyTrashForm.de , httpError = Messages.Comp.HttpError.de , save = "Speichern" , saveSettings = "Einstellungen speichern" @@ -94,7 +102,9 @@ de = , startNow = "Jetzt starten" , languageLabel = Messages.Data.Language.de , classifierTaskStarted = "Kategorisierung gestartet." + , emptyTrashTaskStarted = "Papierkorb löschen gestartet." , fulltextReindexSubmitted = "Volltext Neu-Indexierung gestartet." , fulltextReindexOkMissing = "Bitte tippe OK in das Feld ein, wenn Du wirklich den Index neu erzeugen möchtest." + , emptyTrash = "Papierkorb löschen" } diff --git a/modules/webapp/src/main/elm/Messages/Comp/EmptyTrashForm.elm b/modules/webapp/src/main/elm/Messages/Comp/EmptyTrashForm.elm new file mode 100644 index 00000000..872da608 --- /dev/null +++ b/modules/webapp/src/main/elm/Messages/Comp/EmptyTrashForm.elm @@ -0,0 +1,38 @@ +{- + Copyright 2020 Docspell Contributors + + SPDX-License-Identifier: GPL-3.0-or-later +-} + + +module Messages.Comp.EmptyTrashForm exposing + ( Texts + , de + , gb + ) + +import Messages.Basics +import Messages.Comp.CalEventInput + + +type alias Texts = + { basics : Messages.Basics.Texts + , calEventInput : Messages.Comp.CalEventInput.Texts + , schedule : String + } + + +gb : Texts +gb = + { basics = Messages.Basics.gb + , calEventInput = Messages.Comp.CalEventInput.gb + , schedule = "Schedule" + } + + +de : Texts +de = + { basics = Messages.Basics.de + , calEventInput = Messages.Comp.CalEventInput.de + , schedule = "Zeitplan" + }