diff --git a/cli/Cargo.lock b/cli/Cargo.lock index d11d82982..6d14434ce 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -570,6 +570,7 @@ dependencies = [ "hyper-tls 0.5.0", "hyper-util", "lazy_static", + "mime_guess", "once_cell", "paris", "predicates", @@ -1481,6 +1482,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.8.0" @@ -2706,6 +2717,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicase" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" + [[package]] name = "unicode-bidi" version = "0.3.17" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index cade562c6..d008a3cd9 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -48,6 +48,7 @@ testcontainers = "0.23.1" slug = "0.1.5" regex = "1.11.1" zip = "2.2.1" +mime_guess = "2.0.5" [dependencies.uuid] version = "1.7.0" diff --git a/cli/src/commands/assets.rs b/cli/src/commands/assets.rs index 74db8ba1e..4c00488f6 100644 --- a/cli/src/commands/assets.rs +++ b/cli/src/commands/assets.rs @@ -1,7 +1,7 @@ use std::{ fs::{self, File}, io::Read, - path::PathBuf, + path::{Path, PathBuf}, str::FromStr, }; @@ -18,6 +18,7 @@ use crate::{ use bytes::Bytes; use hyper::header; +use mime_guess::mime; use serde::{Deserialize, Serialize}; use walkdir::WalkDir; @@ -126,21 +127,24 @@ async fn add( let project = get_default_project()?; - let mut file = File::open( - PathBuf::from_str(&project.path) - .unwrap() - .join("assets") - .join(path.unwrap_or("".to_string())) - .join(filename), - ) - .map_err(|err| DaikokuCliError::FileSystem(err.to_string()))?; + let filepath = PathBuf::from_str(&project.path) + .unwrap() + .join("assets") + .join(path.unwrap_or("".to_string())) + .join(filename); + let mut file = + File::open(filepath.clone()).map_err(|err| DaikokuCliError::FileSystem(err.to_string()))?; let mut contents = Vec::new(); let _ = file .read_to_end(&mut contents) .map_err(|err| DaikokuCliError::FileSystem(err.to_string())); - let _ = daikoku_cms_api_post(&url, Bytes::from(contents), false).await?; + let content_type = mime_guess::from_path(filepath.to_string_lossy().into_owned()) + .first() + .unwrap_or(mime::APPLICATION_OCTET_STREAM); + + let _ = daikoku_cms_api_post(&url, Bytes::from(contents), false, Some(content_type)).await?; logger::success("New asset has been pushed".to_string()); @@ -327,6 +331,7 @@ async fn sync() -> DaikokuResult<()> { DaikokuCliError::ParsingError("failed to convert assets to json array".to_string()) })?), false, + None ) .await?; diff --git a/cli/src/commands/cms.rs b/cli/src/commands/cms.rs index cd82799d1..3a7856350 100644 --- a/cli/src/commands/cms.rs +++ b/cli/src/commands/cms.rs @@ -472,12 +472,6 @@ async fn migrate(name: String, path: String, server: String, apikey: String) -> let sources_path = project_path.join("src"); - let root_mail_tenant = bytes_to_struct::( - raw_daikoku_cms_api_get("/tenants/default", &server, &apikey) - .await? - .response, - )?; - let root_mail_user_translations = bytes_to_struct::( raw_daikoku_cms_api_get( "/translations/_mail?domain=tenant.mail.template", @@ -507,7 +501,6 @@ async fn migrate(name: String, path: String, server: String, apikey: String) -> .filter(|api| !EXCLUDE_API.contains(&api._id.as_str())) .collect(); - create_mail_tenant(root_mail_tenant, sources_path.clone())?; create_mail_folder(root_mail_user_translations, sources_path.clone(), true)?; create_mail_folder(mail_user_template, sources_path.clone(), false)?; @@ -616,30 +609,6 @@ pub(crate) fn create_api_folder( Ok(created) } -pub(crate) fn create_mail_tenant( - mail_settings: TenantMailBody, - project_path: PathBuf, -) -> DaikokuResult<()> { - let filename = "page.html".to_string(); - - let file_path = project_path - .clone() - .join(get_mail_page_path(&filename, true).unwrap()); - - let _ = create_path_and_file( - file_path, - mail_settings - .mailer_settings - .map(|mailer| mailer.template.unwrap_or("".to_string())) - .unwrap_or("".to_string()), - filename, - HashMap::new(), - SourceExtension::HTML, - ); - - Ok(()) -} - pub(crate) fn create_mail_folder( intl_translation: IntlTranslationBody, project_path: PathBuf, diff --git a/cli/src/commands/pull.rs b/cli/src/commands/pull.rs index 035c306e1..a5ddfe758 100644 --- a/cli/src/commands/pull.rs +++ b/cli/src/commands/pull.rs @@ -10,8 +10,8 @@ use crate::{ use super::{ cms::{ - self, create_api_folder, create_mail_folder, create_mail_tenant, Api, CmsPage, - IntlTranslationBody, TenantMailBody, EXCLUDE_API, + self, create_api_folder, create_mail_folder, Api, CmsPage, IntlTranslationBody, + TenantMailBody, EXCLUDE_API, }, environments::{get_default_environment, read_apikey_from_secrets}, }; @@ -81,10 +81,6 @@ async fn mails_synchronization(project: &cms::Project) -> DaikokuResult<()> { .collect::>(); if existing_emails_pages.is_empty() { - let root_mail_tenant = bytes_to_struct::( - daikoku_cms_api_get("/tenants/default").await?.response, - )?; - let root_mail_user_translations = bytes_to_struct::( daikoku_cms_api_get("/translations/_mail?domain=tenant.mail.template") .await? @@ -97,7 +93,6 @@ async fn mails_synchronization(project: &cms::Project) -> DaikokuResult<()> { .response, )?; - create_mail_tenant(root_mail_tenant, sources_path.clone())?; create_mail_folder(root_mail_user_translations, sources_path.clone(), true)?; create_mail_folder(mail_user_template, sources_path.clone(), false)?; } else { @@ -107,15 +102,16 @@ async fn mails_synchronization(project: &cms::Project) -> DaikokuResult<()> { .join(item.path.clone().unwrap().replacen("/", "", 1)) .join("page.html"); - let mut file = std::fs::OpenOptions::new() + let file = std::fs::OpenOptions::new() .write(true) .truncate(true) - .open(file_path) - .unwrap(); + .open(file_path); - let _ = file.write_all(item.content.clone().as_bytes()); + if let Ok(mut email) = file { + let _ = email.write_all(item.content.clone().as_bytes()); - let _ = file.flush(); + let _ = email.flush(); + } }); } diff --git a/cli/src/commands/push.rs b/cli/src/commands/push.rs index 4bdb4e5c8..fae30e539 100644 --- a/cli/src/commands/push.rs +++ b/cli/src/commands/push.rs @@ -111,7 +111,7 @@ async fn synchronization(body: &mut Vec, dry_run: bool) -> DaikokuResul ); if !dry_run { - daikoku_cms_api_post("/sync", body, true).await?; + daikoku_cms_api_post("/sync", body, true, None).await?; } Ok(()) diff --git a/cli/src/commands/watch.rs b/cli/src/commands/watch.rs index d48187f0c..3c2ec29eb 100644 --- a/cli/src/commands/watch.rs +++ b/cli/src/commands/watch.rs @@ -1,3 +1,4 @@ +use hyper::header::{HeaderValue, LOCATION}; use regex::Regex; use std::collections::HashMap; use std::io::Read; @@ -8,7 +9,7 @@ use http_body_util::{BodyExt, Empty, Full}; use hyper::body::Bytes; use hyper::server::conn::http1; use hyper::service::service_fn; -use hyper::{header, Method}; +use hyper::{header, Method, StatusCode}; use hyper::{Request, Response}; use hyper_util::rt::TokioIo; @@ -110,8 +111,18 @@ async fn watcher( ) -> Result>, DaikokuCliError> { let uri = req.uri().path().to_string(); - if uri.starts_with("/api/") || uri.starts_with("/tenant-assets/") { - logger::println("forward to api or /tenant-assets".to_string()); + if uri.starts_with("/tenant-assets/") { + let redirect_url = "http://localhost:5173/tenant-assets/api3.jpeg"; + + let mut response = Response::new(Full::::new(Bytes::from(""))); + *response.status_mut() = StatusCode::FOUND; // 302 status + response + .headers_mut() + .insert(LOCATION, HeaderValue::from_str(redirect_url).unwrap()); + + Ok(response) + } else if uri.starts_with("/api/") { + logger::println("forward to api".to_string()); forward_api_call(uri, req, environment).await } else { let path = uri.replace("_/", ""); @@ -242,13 +253,12 @@ async fn forward_api_call( let url: String = format!("{}{}", environment.server, uri); - let cookie = read_cookie_from_environment(true)?; - let raw_req = Request::builder() .method(Method::from_str(&method).unwrap()) .uri(&url) .header(header::HOST, &host) - .header(header::COOKIE, cookie); + .header("Accept", "*/*") + .header(header::COOKIE, read_cookie_from_environment(true)?); let req = if method == "GET" { raw_req.body(Empty::::new().boxed()).unwrap() @@ -292,7 +302,9 @@ async fn forward_api_call( let ( hyper::http::response::Parts { - headers: _, status, .. + headers: _headers, + status, + .. }, body, ) = upstream_resp.into_parts(); @@ -301,6 +313,8 @@ async fn forward_api_call( let status = status.as_u16(); + println!("{:?}", _headers); + if status >= 300 && status < 400 { Ok(Response::new(Full::new(Bytes::from( "Authentication needed! Refresh this page once done", @@ -340,7 +354,9 @@ async fn render_page( fields.insert(param.key, param.value); } - if watch_path.starts_with("/mails") { + if watch_path.starts_with("/mails") + && !watch_path.starts_with("/mails/root/tenant-mail-template") + { let language = if watch_path.contains("/fr") { "fr" } else { @@ -449,7 +465,6 @@ async fn render_page( let source = src.replace('"', """); - let children: String = if SourceExtension::from_str(&page.content_type()).unwrap() == SourceExtension::HTML { diff --git a/cli/src/helpers.rs b/cli/src/helpers.rs index de498f052..ef7d4f2a3 100644 --- a/cli/src/helpers.rs +++ b/cli/src/helpers.rs @@ -2,6 +2,7 @@ use std::any::type_name; use bytes::Buf; use hyper::header; +use mime_guess::Mime; use serde::Deserialize; use crate::{ @@ -45,6 +46,7 @@ pub(crate) async fn daikoku_cms_api_post( path: &str, body: T, is_json_content: bool, + content_type: Option, ) -> DaikokuResult> where reqwest::Body: From, @@ -60,18 +62,24 @@ where let url: String = format!("{}/cms-api{}", environment.server, &path); - let builder = reqwest::Client::new().post(url).header(header::HOST, host); + let mut builder = reqwest::Client::new().post(url).header(header::HOST, host); - let resp = if is_json_content { + builder = if is_json_content { builder.header(header::CONTENT_TYPE, "application/json") } else { builder } - .header(header::AUTHORIZATION, format!("Basic {}", apikey)) - .body(body) - .send() - .await - .map_err(|err| DaikokuCliError::DaikokuStrError(err.to_string()))?; + .header(header::AUTHORIZATION, format!("Basic {}", apikey)); + + if let Some(content) = content_type { + builder = builder.header("Asset-Content-Type", content.to_string()); + } + + let resp = builder + .body(body) + .send() + .await + .map_err(|err| DaikokuCliError::DaikokuStrError(err.to_string()))?; let status = resp.status().as_u16(); diff --git a/daikoku/app/controllers/HomeController.scala b/daikoku/app/controllers/HomeController.scala index 01a53351e..f9ed8b16c 100644 --- a/daikoku/app/controllers/HomeController.scala +++ b/daikoku/app/controllers/HomeController.scala @@ -284,8 +284,6 @@ class HomeController( true ) -// println(strictPage) - val (page, urlSearchParams) = if (strictPage._1.nonEmpty) strictPage diff --git a/daikoku/app/services/AssetsService.scala b/daikoku/app/services/AssetsService.scala index 4056f52cc..d67fb8666 100644 --- a/daikoku/app/services/AssetsService.scala +++ b/daikoku/app/services/AssetsService.scala @@ -1,13 +1,14 @@ package fr.maif.otoroshi.daikoku.services import fr.maif.otoroshi.daikoku.actions.ApiActionContext +import fr.maif.otoroshi.daikoku.audit.AuditTrailEvent +import fr.maif.otoroshi.daikoku.ctrls.CmsApiActionContext import fr.maif.otoroshi.daikoku.domain.{Asset, AssetId} import fr.maif.otoroshi.daikoku.env.Env import fr.maif.otoroshi.daikoku.logger.AppLogger import fr.maif.otoroshi.daikoku.services.NormalizeSupport.normalize import fr.maif.otoroshi.daikoku.utils.IdGenerator import fr.maif.otoroshi.daikoku.utils.StringImplicits.BetterString -import org.apache.pekko.http.scaladsl.model.{ContentType, ContentTypes, HttpResponse} import org.apache.pekko.http.scaladsl.util.FastFuture import org.apache.pekko.stream.connectors.s3.ObjectMetadata import org.apache.pekko.stream.scaladsl.{Sink, Source} @@ -152,10 +153,6 @@ class AssetsService { .flatMap(slug => if (slug.isEmpty) None else Some(slug)) val assetId = AssetId(IdGenerator.uuid) - println(contentType) - - Future.successful(Ok(Json.obj())) - ctx.tenant.bucketSettings match { case None => FastFuture.successful( @@ -386,7 +383,6 @@ class AssetsService { ) = { implicit val ec = env.defaultExecutionContext - ctx.tenant.bucketSettings match { case None => FastFuture.successful( @@ -420,38 +416,28 @@ class AssetsService { case Some(_) if download => env.assetsStore .getTenantAsset(ctx.tenant.id, AssetId(assetId))(cfg) - .map { case (metadata, data, s3Source) => - -// case None => -// NotFound(Json.obj("error" -> "Asset not found!")) -// case Some((source, meta)) => -// val filename = meta.metadata -// .filter(_.name().startsWith("x-amz-meta-")) -// .find(_.name() == "x-amz-meta-filename") -// .map(_.value()) -// .getOrElse("asset.txt") - -// Ok.send( - val entity = org.apache.pekko.http.scaladsl.model.HttpEntity( - metadata.contentType - .flatMap(ContentType.parse(_).toOption) - .getOrElse(ContentTypes.`application/octet-stream`), - metadata.contentLength, - s3Source) - - Ok.sendEntity(HttpEntity.Streamed(entity, None, None)) - -// ) -// HttpEntity.Streamed( -// Source.single(source), -// None, -// meta.contentType -// .map(Some.apply) -// .getOrElse(Some("application/octet-stream")) -// ) -// .withHeaders( -// "Content-Disposition" -> s"""attachment; filename="$filename"""" -// ) + .map { + case None => + NotFound(Json.obj("error" -> "Asset not found!")) + case Some((source, meta)) => + val filename = meta.metadata + .filter(_.name().startsWith("x-amz-meta-")) + .find(_.name() == "x-amz-meta-filename") + .map(_.value()) + .getOrElse("asset.txt") + + Ok.sendEntity( + HttpEntity.Streamed( + source, + None, + meta.contentType + .map(Some.apply) + .getOrElse(Some("application/octet-stream")) + ) + ) + .withHeaders( + "Content-Disposition" -> s"""attachment; filename="$filename"""" + ) } case Some(url) => env.wsClient @@ -480,4 +466,4 @@ class AssetsService { } } -} +} \ No newline at end of file diff --git a/daikoku/app/utils/s3.scala b/daikoku/app/utils/s3.scala index a1125a63c..dccad54af 100644 --- a/daikoku/app/utils/s3.scala +++ b/daikoku/app/utils/s3.scala @@ -3,11 +3,14 @@ package fr.maif.otoroshi.daikoku.utils import com.amazonaws.auth.{AWSStaticCredentialsProvider, BasicAWSCredentials} import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration import com.amazonaws.services.s3.AmazonS3ClientBuilder -import com.amazonaws.services.s3.model.PutObjectRequest import com.amazonaws.{ClientConfiguration, HttpMethod, SdkClientException} import fr.maif.otoroshi.daikoku.domain._ import org.apache.pekko.actor.ActorSystem -import org.apache.pekko.http.scaladsl.model.{ContentType, ContentTypes, HttpHeader} +import org.apache.pekko.http.scaladsl.model.{ + ContentType, + ContentTypes, + HttpHeader +} import org.apache.pekko.stream.Materializer import org.apache.pekko.stream.connectors.s3.headers.CannedAcl import org.apache.pekko.stream.connectors.s3.scaladsl.S3 @@ -120,9 +123,7 @@ class AssetsDataStore(actorSystem: ActorSystem)(implicit override def getRegion: Region = Region.of(conf.region) }, listBucketApiVersion = ApiVersion.ListBucketVersion2 - ) - .withEndpointUrl(conf.endpoint) - .withAccessStyle(AccessStyle.PathAccessStyle) + ).withEndpointUrl(conf.endpoint) S3Attributes.settings(settings) } @@ -141,7 +142,6 @@ class AssetsDataStore(actorSystem: ActorSystem)(implicit val ctype = ContentType .parse(contentType) .getOrElse(ContentTypes.`application/octet-stream`) - val meta = MetaHeaders( Map( "filename" -> name, @@ -153,14 +153,6 @@ class AssetsDataStore(actorSystem: ActorSystem)(implicit "content-type" -> ctype.value ) ) - - lazy val opts = new ClientConfiguration() - lazy val endpointConfiguration = - new EndpointConfiguration(conf.endpoint, conf.region) - lazy val credentialsProvider = new AWSStaticCredentialsProvider( - new BasicAWSCredentials(conf.access, conf.secret) - ) - val sink = S3 .multipartUpload( bucket = conf.bucket, @@ -288,7 +280,7 @@ class AssetsDataStore(actorSystem: ActorSystem)(implicit contentType = ctype, metaHeaders = meta, cannedAcl = CannedAcl.Private, // CannedAcl.PublicRead - chunkingParallelism = 1, + chunkingParallelism = 1 ) .withAttributes(s3ClientSettingsAttrs) content.toMat(sink)(Keep.right).run() @@ -322,24 +314,17 @@ class AssetsDataStore(actorSystem: ActorSystem)(implicit val attrs = s3ClientSettingsAttrs S3.listBucket(conf.bucket, Some(s"/${tenant.value}/tenant-assets")) .mapAsync(1) { content => -// S3.getObjectMetadata(conf.bucket, content.key) -// .withAttributes(s3ClientSettingsAttrs) -// .toMat(Sink.head)(Keep.both) -// .run() -// ._2 -// .map(meta => S3ListItem(content, meta.getOrElse(ObjectMetadata(Seq.empty)))) - val (metadataFuture, _dataFuture) = S3.getObject( - conf.bucket, - content.key + val none: Option[ObjectMetadata] = None + S3.getObjectMetadata(conf.bucket, content.key) + .withAttributes(attrs) + .runFold(none)((_, opt) => opt) + .map { + case None => + S3ListItem( + content, + ObjectMetadata(collection.immutable.Seq.empty[HttpHeader]) ) - .withAttributes(s3ClientSettingsAttrs) - .toMat(Sink.head)(Keep.both) - .run() - - for { - metadata <- metadataFuture - } yield { - S3ListItem(content, metadata) + case Some(meta) => S3ListItem(content, meta) } } .withAttributes(attrs) @@ -360,45 +345,11 @@ class AssetsDataStore(actorSystem: ActorSystem)(implicit def getTenantAsset(tenant: TenantId, asset: AssetId)(implicit conf: S3Configuration - ): Future[(ObjectMetadata, ByteString, Source[ByteString, Future[ObjectMetadata]])] = { -// val none: Option[(Source[ByteString, NotUsed], ObjectMetadata)] = None - -// val s3Source: Source[ByteString, Future[ObjectMetadata]] = S3.getObject(conf.bucket, s"/${tenant.value}/tenant-assets/${asset.value}") -// s3Source - val s3Source: Source[ByteString, Future[ObjectMetadata]] = - S3.getObject(conf.bucket, s"/${tenant.value}/tenant-assets/${asset.value}") - - val (metadataFuture, dataFuture) = - s3Source.toMat(Sink.head)(Keep.both).run() - - for { - m <- metadataFuture - d <- dataFuture - } yield { - (m, d, s3Source) - } - -// val (meta, data) = S3.getObject(conf.bucket, s"/${tenant.value}/tenant-assets/${asset.value}") -// .withAttributes(s3ClientSettingsAttrs) -// .toMat(Sink.head)(Keep.both) -// .run() -// -// for { -// m <- meta -// d <- data -// } yield { -// (d, m) -// } - -// S3.getObject(conf.bucket, s"/${tenant.value}/tenant-assets/${asset.value}") -// .withAttributes(s3ClientSettingsAttrs) -// .runFold(none) { case (meta, data) => -// -// println(meta) -// println(data) -// -// meta -// } + ): Future[Option[(Source[ByteString, NotUsed], ObjectMetadata)]] = { + val none: Option[(Source[ByteString, NotUsed], ObjectMetadata)] = None + S3.getObject(conf.bucket, s"/${tenant.value}/tenant-assets/${asset.value}") + .withAttributes(s3ClientSettingsAttrs) + .runFold(none)((opt, _) => opt) } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -526,4 +477,4 @@ class AssetsDataStore(actorSystem: ActorSystem)(implicit getPresignedUrl(path) } -} +} \ No newline at end of file diff --git a/daikoku/javascript/src/components/adminbackoffice/cms/Body.tsx b/daikoku/javascript/src/components/adminbackoffice/cms/Body.tsx deleted file mode 100644 index bd4756fcc..000000000 --- a/daikoku/javascript/src/components/adminbackoffice/cms/Body.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Form, FormRef, type } from '@maif/react-forms'; -import React, { useContext, useEffect, useImperativeHandle, useRef, useState } from 'react'; - -import { I18nContext } from '../../../contexts'; -import { ContentSideView } from './ContentSideView'; -import DragAndDropWrapper from './DragAndDropWrapper'; - -export type BodyRef = { - handleSubmit: () => void -} -export default React.forwardRef(({ contentType, setFinalValue, show, pages, inValue, publish, history, editable }, ref) => { - const { translate } = useContext(I18nContext); - const r = useRef(); - - useEffect(() => { - setValue({ draft: inValue || '' }); - }, [inValue]); - - const [value, setValue] = useState({}); - - useImperativeHandle(ref, () => ({ - handleSubmit() { - r.current?.handleSubmit(); - }, - })); - - const handleDrop = (file: any) => { - const reader = new FileReader(); - reader.onload = (e) => { - const text = e.target?.result; - setValue({ draft: text }); - }; - reader.readAsText(file); - }; - - const schema = { - draft: { - type: type.string, - label: null, - help: translate('cms.create.draft_help'), - render: (formProps: any) => { - const [draft, setDraft] = useState(''); - - useEffect(() => { - setDraft(value.draft); - }, [value.draft]); - - return ( - - { - setDraft(e); - formProps.onChange(e); - }} - /> - - ); - }, - }, - }; - - const flow = ['draft']; - - return ( -
-
{ - setValue(body); - setFinalValue(body); - }} - ref={r} - footer={() => <>} - /> -
- ); -} -); diff --git a/daikoku/javascript/src/components/adminbackoffice/cms/ContentSideView.tsx b/daikoku/javascript/src/components/adminbackoffice/cms/ContentSideView.tsx deleted file mode 100644 index 0aea0c480..000000000 --- a/daikoku/javascript/src/components/adminbackoffice/cms/ContentSideView.tsx +++ /dev/null @@ -1,431 +0,0 @@ -import { CodeInput, SelectInput } from '@maif/react-forms'; -import RefAutoComplete from 'antd/lib/auto-complete'; -import React, { useContext, useEffect, useRef, useState } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; -import Select, { SingleValue } from 'react-select'; -import { ModalContext } from '../../../contexts'; - -import { I18nContext } from '../../../contexts'; -import Editor from './Editor'; -import Helpers from './helpers.json'; - -const CONTENT_TYPES_TO_MODE = { - 'application/json': 'json', - 'text/html': 'html', - 'text/javascript': 'javascript', - 'text/css': 'css', - 'text/markdown': 'mardown', - 'text/plain': 'plain_text', - 'text/xml': 'xml', -}; - -const LinksView = ({ - editor, - onChange -}: any) => { - const { translate } = useContext(I18nContext); - - return ( -
- {translate('cms.content_side_view.choose_link')} - - {(setShow: any) => ({ - label: page.name, - value: page.id - }))} - onChange={(page: SingleValue<{ label: string, value: string }>) => { - setShow(true); - onChange(); - copy(editor, `{{${prefix} "${page?.value}"}}`); - }} - />} - -
-); - -const TopActions = ({ - setSideView, - publish, - setSelector -}: any) => { - const { translate } = useContext(I18nContext); - const { confirm } = useContext(ModalContext); - const navigate = useNavigate(); - const select = (id: any) => { - setSelector(undefined); - setSideView(true); - }; - - return ( -
- -
- - -
-
- ); -}; - -const HelperView = ({ - content, - onChange, - editor -}: any) => { - const [value, setValue] = useState(content.example); - const { translate } = useContext(I18nContext); - - useEffect(() => { - setValue(content.example); - }, [content.example]); - - return ( -
-
{translate(`cms.content_side_view.${content.name}`)}
- {content.parameters && ( -
-
Parameters
-
    - {(content.parameters || []).map((name: any) =>
  • {name}
  • )} -
-
- )} - {content.link && ( - - Link to the model - - )} - - -
- ); -}; - -export const ContentSideView = ({ - value, - onChange, - pages, - publish, - contentType, - editable -}: any) => { - const { translate } = useContext(I18nContext); - const [sideView, setSideView] = useState(false); - const [selector, setSelector] = useState(''); - const [search, setSearch] = useState(''); - const [helpersList, setHelpers] = useState([]); - - const editorRef = useRef(); - - const [selectedPage, setSelectedPage] = useState({ - top: 0, - left: 0, - pageName: undefined, - }); - - const [height, setHeight] = useState(500); - - useEffect(() => { - setHelpers( - Helpers.reduce( - (acc, curr) => ({ - ...acc, - - [curr.important || curr.category]: { - collapsed: true, - helpers: [ - ...((acc[curr.important || curr.category] || {}).helpers || []), - { - ...curr, - term: translate(`cms.content_side_view.${curr.name}`) - .toLowerCase() - .replace(/[\[\]&]+/g, ''), - }, - ], - } - }), {} - ) - ); - - searchHeight(); - }, []); - - const searchHeight = () => { - const elem = document.getElementById('content_sideview_parent') - if (!elem) { - setTimeout(searchHeight, 250); - } else { - setHeight(window.innerHeight - elem.getBoundingClientRect().top - 75); - } - }; - - //@ts-ignore //FIXME??? - window.pages = pages; - - const navigate = useNavigate() - - const filterHelpers = (value: any) => { - const term = value.toLowerCase().replace(/[\[\]&]+/g, ''); - setSearch(value); - - setHelpers(Object.fromEntries(Object.entries(helpersList).map(([g, { helpers, ...rest }]: any) => [ - g, - { - ...rest, - collapsed: term.length > 0 ? false : true, - helpers: (helpers as any).map((helper: any) => ({ - ...helper, - filtered: term.length === 0 ? false : !helper.term.includes(term) - })), - }, - ]))); - }; - - return (
- - {editable && <> - - - {translate('cms.body.drag_and_drop_advice')} - - } -
- {selectedPage.pageName && ( - setSelectedPage({ pageName: undefined })}> - {`${translate('cms.content_side_view.edit')} ${selectedPage.pageName}`} - )} - { - editorRef.current = editorInstance - // editorInstance.container.style.resize = 'both'; - // document.addEventListener('mouseup', (e) => editorInstance.resize()); - }} mode={CONTENT_TYPES_TO_MODE[contentType] || 'html'} height={height} width="-1" /> - {sideView && (
-
- {selector !== 'history' && (
-
- filterHelpers(e.target.value)} style={{ border: 'none' }} /> -
-
- {Object.entries(helpersList) - .map(([groupName, { helpers, collapsed }]: any) => ( -
setHelpers(Object.fromEntries(Object.entries(helpersList).map(([g, { collapsed, ...rest }]: any) => { - if (g === groupName) - return [ - g, - { - ...rest, - collapsed: !collapsed, - helpers: rest.helpers.map((helper: any) => ({ - ...helper, - filtered: false - })), - }, - ]; - return [g, { ...rest, collapsed }]; - })))}> - {helpers.filter((helper) => !helper.filtered).length > 0 && ( -
- {groupName} - -
- )} - {!collapsed && - helpers.filter((helper) => !helper.filtered) - .map((helper) => ( - - ))} -
))} -
-
)} -
- setSideView(false)} /> - {(selector as any)?.name === 'links' && ( setSideView(false)} />)} - {(selector as any)?.name === 'pages' && ( setSideView(false)} />)} - {(selector as any)?.name === 'blocks' && ( setSideView(false)} />)} - {((selector as any)?.name.startsWith('daikoku') || - !['links', 'blocks', 'pages'].includes((selector as any)?.name)) && - selector && ( setSideView(false)} content={selector} />)} -
-
-
)} -
-
); -}; diff --git a/daikoku/javascript/src/components/adminbackoffice/cms/Create.tsx b/daikoku/javascript/src/components/adminbackoffice/cms/Create.tsx index 9e3cca6a5..99e51bc77 100644 --- a/daikoku/javascript/src/components/adminbackoffice/cms/Create.tsx +++ b/daikoku/javascript/src/components/adminbackoffice/cms/Create.tsx @@ -1,191 +1,63 @@ -import React, { useContext, useEffect, useRef, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { I18nContext } from '../../../contexts'; import { useNavigate, useParams } from 'react-router-dom'; import * as Services from '../../../services'; import { getApolloContext } from '@apollo/client'; -import Sidebar, { SideBarRef } from './Sidebar'; -import Body, { BodyRef } from './Body'; -import { ModalContext } from '../../../contexts'; import { Spinner } from '../../utils/Spinner'; +import { Form, format, type } from '@maif/react-forms'; export const Create = (props: any) => { const { client } = useContext(getApolloContext()); const { translate } = useContext(I18nContext); - const { alert } = useContext(ModalContext) const params = useParams(); const navigate = useNavigate(); const [loading, setLoading] = useState(true); - const [tab, setTab] = useState(0); - const sideRef = useRef(null); - const bodyRef = useRef(null); - - const [inValue, setInValue] = useState({}); - const [savePath, setSavePath] = useState(); - const [contentType, setContentType] = useState(); - - const [finalSideValue, setFinalSideValue] = useState(); - const [finalBodyValue, setFinalBodyValue] = useState(); - const [action, setFormAction] = useState(); + const [value, setValue] = useState({}); + // const [contentType, setContentType] = useState(); useEffect(() => { const id = params.id; if (id) { setLoading(true); - //FIXME handle client is not setted client && client.query({ query: Services.graphql.getCmsPage(id) }).then((res) => { if (res.data) { - const { draft, history, ...side } = res.data.cmsPage; - setFinalBodyValue(undefined); - setFinalSideValue(undefined); - setFormAction(undefined); - setInValue({ - side: { - ...side, - metadata: side.metadata ? JSON.parse(side.metadata) : {}, - isBlockPage: !side.path || side.path.length === 0, - }, - history, - draft, - }); - setContentType(side.contentType); - setSavePath(side.path); + const { history, ...side } = res.data.cmsPage; + setValue(side.body); + // setContentType(side.contentType); } setLoading(false); }); } }, [params.id]); - useEffect(() => { - const onUpdatePreview = action === 'update_before_preview'; - const onPublish = action === 'publish'; - - if ((action === 'update' || onUpdatePreview || onPublish) && finalSideValue && finalBodyValue) { - if (onPublish) { - const lastPublishedDate = Date.now(); - const updatedPage = { - ...finalSideValue, - ...finalBodyValue, - body: (finalBodyValue as any).draft, - lastPublishedDate, - }; - Services.createCmsPage(params.id, updatedPage).then(() => { - const { draft, ...side } = updatedPage; - setInValue({ - draft, - side, - }); - reset(); - }); - } else { - Services.createCmsPage(params.id, { - ...finalSideValue, - ...finalBodyValue, - }).then((res) => { - reset(); - if (!res.error && !params.id) - navigate('/settings/pages', { - state: { - reload: true, - }, - }); - else if (res.error) { - alert({ message: res.error }); - } else setSavePath((finalSideValue as any).path); - - if (onUpdatePreview) setTimeout(() => setTab(1), 100); - }); - } - } - }, [action, finalSideValue, finalBodyValue]); - - const reset = () => { - setFormAction(undefined); - setFinalSideValue(undefined); - setFinalBodyValue(undefined); - }; - - const updatePage = () => { - setFormAction('update'); - [bodyRef, sideRef].map((r) => r.current?.handleSubmit()); - }; - - const onPublish = () => { - setFormAction('publish'); - [bodyRef, sideRef].map((r) => r.current?.handleSubmit()); - }; - - const TabButton = ({ - title, - onClose, - onClick, - selected - }: any) => ( -
- - {onClose && } -
- ); if (loading) { return ; } - const editable = !inValue.side.metadata.from; - - return ( -
- {editable && } -
- {editable &&
- {[ - { title: translate('cms.create.draft'), id: 0, showPreview: () => setTab(0) }, - { - title: translate('cms.create.draft_preview'), - id: 1, - showPreview: () => { - setFormAction('update_before_preview'); - [bodyRef, sideRef].map((r) => r.current?.handleSubmit()); - }, - }, - { title: translate('cms.create.content'), id: 2 }, - ].map(({ title, id, showPreview }) => !savePath ? null : ( { - if (showPreview) - showPreview(); - else { - setFormAction(undefined); - [bodyRef, sideRef].map((r) => r.current?.handleSubmit()); - setTab(id); - } - }} />))} -
} - - {tab === 1 && (