From 8665a3f8adf8b7658297524d68a0524864caed3e Mon Sep 17 00:00:00 2001 From: Simon Laux Date: Wed, 15 Apr 2020 22:45:20 +0200 Subject: [PATCH] write zip file, pack blobs into it and copy some styles over from desktop --- Cargo.lock | 40 +++ Cargo.toml | 1 + assets/exported-chat.css | 760 +++++++++++++++++++++++++++++++++++++++ examples/repl/cmdline.rs | 15 +- src/export_chat.rs | 151 +++++--- 5 files changed, 921 insertions(+), 46 deletions(-) create mode 100644 assets/exported-chat.css diff --git a/Cargo.lock b/Cargo.lock index f1c9513eb..faaab85d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -373,6 +373,24 @@ name = "bytes" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bzip2" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bzip2-sys 0.1.8+1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.8+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "c2-chacha" version = "0.2.3" @@ -678,6 +696,7 @@ dependencies = [ "strum_macros 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread-local-object 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zip 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1903,6 +1922,11 @@ dependencies = [ "inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "podio" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ppv-lite86" version = "0.2.6" @@ -3136,6 +3160,18 @@ dependencies = [ "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "zip" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + [metadata] "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" "checksum aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "54eb1d8fe354e5fc611daf4f2ea97dd45a765f4f1e4512306ec183ae2e8f20c9" @@ -3180,6 +3216,8 @@ dependencies = [ "checksum bytecount 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b92204551573580e078dc80017f36a213eb77a0450e4ddd8cfa0f3f2d1f0178f" "checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" "checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" +"checksum bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b" +"checksum bzip2-sys 0.1.8+1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "05305b41c5034ff0e93937ac64133d109b5a2660114ec45e9760bc6816d83038" "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" "checksum cargo_metadata 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e5d1b4d380e1bab994591a24c2bdd1b054f64b60bef483a8c598c7c345bc3bbe" "checksum cast5 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ce5759b4c52ca74f9a98421817c882f1fd9b0071ae41cd61ab9f9d059c04fd6" @@ -3343,6 +3381,7 @@ dependencies = [ "checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" "checksum png 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef859a23054bbfee7811284275ae522f0434a3c8e7f4b74bd4a35ae7e1c4a283" +"checksum podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "780fb4b6698bbf9cf2444ea5d22411cef2953f0824b98f33cf454ec5615645bd" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" "checksum pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074" @@ -3485,3 +3524,4 @@ dependencies = [ "checksum x25519-dalek 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "637ff90c9540fa3073bb577e65033069e4bae7c79d49d74aa3ffdf5342a53217" "checksum zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" "checksum zeroize_derive 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" +"checksum zip 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6df134e83b8f0f8153a094c7b0fd79dfebe437f1d76e7715afa18ed95ebe2fd7" diff --git a/Cargo.toml b/Cargo.toml index 72cb02b89..44907a1af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ encoded-words = { git = "https://github.com/async-email/encoded-words", branch=" native-tls = "0.2.3" image = { version = "0.22.4", default-features=false, features = ["gif_codec", "jpeg", "ico", "png_codec", "pnm", "webp", "bmp"] } pretty_env_logger = "0.3.1" +zip = "0.5" rustyline = { version = "4.1.0", optional = true } diff --git a/assets/exported-chat.css b/assets/exported-chat.css new file mode 100644 index 000000000..56348af44 --- /dev/null +++ b/assets/exported-chat.css @@ -0,0 +1,760 @@ +:root {--colorPrimary: #42A5F5;--colorDanger: #f96856;--colorNone: #a0a0a0;--ovalButtonBg: #415e6b;--ovalButtonBgHover: rgb(120, 156, 173);--ovalButtonText: #fff;--ovalButtonTextHover: rgb(0, 0, 0);--navBarBackground: #415e6b;--navBarText: #fff;--navBarSearchPlaceholder: rgb(186, 186, 186);--navBarGroupSubtitle: rgb(186, 186, 186);--chatViewBg: #e6dcd4;--chatViewBgImgPath: url(../images/background_light.svg);--composerBg: #fff;--composerText: #010101;--composerPlaceholderText: rgba(1, 1, 1, 0.5);--composerBtnColor: rgba(1, 1, 1, 0.9);--composerSendButton: #415e6b;--emojiSelectorSelectionColor: #2090ea;--chatListItemSelectedBg: #4c6e7d;--chatListItemSelectedBgHover: #5E889B;--chatListItemSelectedText: #fff;--chatListItemBgHover: rgb(228, 228, 228);--chatListBorderColor: #b9b9b9;--chatListBorder: 1px solid undefined;--messageText: #010101;--messageTextLink: #010101;--setupMessageText: #ed824e;--infoMessageBubbleBg: #0000008c;--infoMessageBubbleText: white;--messageIncommingBg: #fff;--messageIncommingDate: #010101;--messageOutgoingBg: #efffde;--messageOutgoingStatusColor: #4caf50;--messageButtons: #8b8e91;--messageButtonsHover: #070c14;--messageStatusIcon: #4caf50;--messageStatusIconSending: #62656a;--messagePadlockOutgoing: #4caf50;--messagePadlockIncomming: #a4a6a9;--messageMetadataDate: #62656a;--messageMetadataIncomming: rgba(#ffffff, 0.7);--messageAuthor: #ffffff;--messageAttachmentIconExtentionColor: #070c14;--messageAttachmentIconBg: transparent;--messageAttachmentFileInfo: #010101;--loginInputFocusColor: #42A5F5;--loginButtonText: #42A5F5;--deltaChatPrimaryFg: #010101;--deltaChatPrimaryFgLight: #62656a;--contextMenuBg: #fff;--contextMenuBorder: rgb(221, 221, 221);--contextMenuText: #62656a;--contextMenuSelected: #f5f5f5;--contextMenuSelectedBg: #a4a6a9;--bp3DialogHeaderBg: #fff;--bp3DialogHeaderIcon: #666666;--bp3DialogBgSecondary: #ececec;--bp3DialogBgPrimary: #fff;--bp3Heading: #010101;--bp3ButtonText: #010101;--bp3ButtonBg: #fff;--bp3ButtonGradientTop: rgba(255,255,255,0.8);--bp3ButtonGradientBottom: rgba(255,255,255,0);--bp3ButtonHoverBg: #ebf1f5;--bp3InputText: #010101;--bp3InputBg: #fff;--bp3InputPlaceholder: lightgray;--bp3MenuText: #010101;--bp3MenuBg: #fff;--bp3Switch: #7a8084;--bp3SwitchShadow: unset;--bp3SwitchChecked: #acd4e8;--bp3SwitchShadowChecked: unset;--bp3SwitchKnob: #f5f5f5;--bp3SwitchKnobShadow: 0px 2px 0 0px #d2cfcfad;--bp3SwitchKnobChecked: #42A5F5;--bp3SwitchKnobShadowChecked: 0px 1px 0 0px #c9d4d2d1;--bp3SpinnerTrack: #acd4e8;--bp3SpinnerHead: #42a5f5;--bp3SelectorTop: rgba(255, 255, 255, 0.8);--bp3SelectorBottom: rgba(255, 255, 255, 0.0);--outlineProperties: 1px solid transparent;--outlineColor: b9b9b9;--emojiMartText: #010101;--emojiMartSearchBorder: lightgrey;--emojiMartBg: #fff;--emojiMartOutsideRadius: 5px;--emojiMartCategoryIcons: rgb(99, 99, 99);--emojiMartInputBg: #f5f5f5;--emojiMartInputText: #010101;--emojiMartInputPlaceholder: rgb(74, 74, 74);--emojiMartSelect: rgb(198, 198, 198);--galleryBg: #fff;--avatarLabelColor: #ffffff;--brokenMediaText: #070c14;--brokenMediaBg: #ffffff;--unreadCountBg: #2090ea;--unreadCountLabel: #ffffff;--contactListItemBg: #62656a;--contactListInitalColor: #62656a;--contactEmailColor: #62656a;--errorColor: #f44336;--globalLinkColor: #2090ea;--globalBackground: #fff;--globalText: #010101;--mapOverlayBg: #fff;--videoPlayBtnIcon: #2090ea;--videoPlayBtnBg: #ffffff;--scrollbarThumb: #666666;--scrollbarThumbHover: #606060;} +* { + box-sizing: border-box; +} +html { + height: 100%; + --messageIncommingBg: rgb(232, 232, 232); +} +body { + position: relative; + height: 100%; + width: 100%; + margin: 0; + font-family: Roboto, "Apple Color Emoji", NotoEmoji, "Helvetica Neue", Arial, + Helvetica, NotoMono, sans-serif !important; + font-size: 14px; + color: black; + background-color: white; +} +ul { + list-style: none; + padding-left: 0; +} + +.GroupImage { + width: 100px; + height: 100px; + cursor: pointer; +} +.RemoveGroupImage { + width: 100px; + margin-bottom: 4px; +} +input:focus { + outline: 0 !important; +} +button:focus { + outline: none; +} +button:focus { + outline: none; +} +::-webkit-scrollbar { + width: 6px; + height: 0; +} +::-webkit-scrollbar-track { + background: white; +} +::-webkit-scrollbar-thumb { + background: var(--scrollbarThumb); +} +::-webkit-scrollbar-thumb:hover { + background: var(--scrollbarThumbHover); +} +::-webkit-scrollbar-corner { + background: transparent; +} +span.module-contact-name { + font-weight: 200; + font-size: medium; +} +.module-contact-name__profile-name { + font-style: italic; +} +.AvatarBubble { + position: relative; + z-index: 2; + object-fit: cover; + height: 48px; + width: 48px; + margin-top: 8px; + margin-bottom: 8px; + border-radius: 100%; + background-color: #505050; + color: var(--avatarLabelColor); + font-size: 26px; + line-height: 48px; + text-align: center; + user-select: none; +} +.AvatarBubble.large { + height: 64px; + width: 64px; + line-height: 64px; + font-size: 39px; +} +.AvatarBubble--NoSearchResults { + transform: rotate(45deg); + line-height: 46px; + letter-spacing: 1px; +} +.AvatarBubble--NoSearchResults::after { + content: ":-("; +} +.AvatarImage { + position: relative; + z-index: 2; + object-fit: cover; + height: 48px; + width: 48px; + margin-top: 8px; + margin-bottom: 8px; + border-radius: 100%; + user-select: none; +} +.AvatarImage.large { + height: 64px; + width: 64px; +} +.attachment-overlay .attachment-view { + height: 100%; + padding: 0; + margin: 0; + display: flex; + align-items: center; + justify-content: center; + background-color: #313131; +} +.attachment-overlay .attachment-view img, +.attachment-overlay .attachment-view video { + width: 100vw; + max-height: 100vh; + object-fit: contain; +} +.attachment-overlay .attachment-view video { + width: 95vw; +} +.attachment-overlay .render-media-wrapper { + width: 100vw; + height: 100vh; +} +.attachment-overlay .btn-wrapper { + float: right; + position: absolute; + z-index: 10; + cursor: pointer; +} +.attachment-overlay .download-btn { + height: 36px; + width: 36px; + display: inline-block; + -webkit-mask: url("../images/download.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: var(--messageButtons); +} +.attachment-overlay .download-btn:hover { + background-color: var(--messageButtons); +} +.message-attachment-media { + text-align: center; + position: relative; + cursor: pointer; + margin-left: -12px; + margin-right: -12px; + margin-top: -10px; + margin-bottom: -10px; + border-radius: 16px; + overflow: hidden; + background-color: var(--messageAttachmentIconBg); +} +.message-attachment-media > .attachment-content { + object-fit: scale-down; + object-position: center; + min-height: 150px; + max-height: 300px; + max-width: 40vw; + margin-bottom: -4px; + cursor: pointer; +} +.message-attachment-media > .video-play-btn { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 48px; + height: 48px; + background-color: var(--videoPlayBtnBg); + border-radius: 24px; +} +.message-attachment-media > .video-play-btn > .video-play-btn-icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + height: 36px; + width: 36px; + -webkit-mask: url("../images/play.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: var(--videoPlayBtnIcon); +} +.message-attachment-media.content-below { + margin-bottom: 7px; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; +} +.message-attachment-media.content-above { + margin-top: 4px; + border-top-left-radius: 0px; + border-top-right-radius: 0px; +} +.message-attachment-broken-media { + font-size: 11px; + line-height: 16px; + letter-spacing: 0.3px; + padding: 10px; + text-align: center; + text-transform: uppercase; + color: var(--brokenMediaBg); +} +.message-attachment-broken-media.incoming { + color: var(--brokenMediaText); +} +.message-attachment-audio { + margin-top: 2px; + display: block; + margin-right: 30px; +} +.message-attachment-audio.content-below { + margin-bottom: 5px; +} +.message-attachment-audio.content-above { + margin-top: 6px; +} +.message-attachment-generic { + display: flex; + flex-direction: row; +} +.message-attachment-generic.content-below { + padding-bottom: 6px; +} +.message-attachment-generic.content-above { + padding-top: 4px; +} +.message-attachment-generic > .file-icon { + background: url("../images/file-gradient.svg") no-repeat center; + height: 44px; + width: 56px; + margin-left: -13px; + margin-right: -14px; + margin-bottom: -4px; + display: flex; + flex-direction: row; + align-items: center; +} +.message-attachment-generic > .file-icon > .file-extension { + font-size: 10px; + line-height: 13px; + letter-spacing: 0.1px; + text-transform: uppercase; + text-align: center; + width: 25px; + margin-left: auto; + margin-right: auto; + overflow-x: hidden; + white-space: nowrap; + text-overflow: clip; + color: var(--messageAttachmentIconExtentionColor); + font-family: monospace; +} +.message-attachment-generic > .text-part { + flex-grow: 1; + margin-left: 8px; + max-width: calc(100% - 37px); +} +.message-attachment-generic > .text-part > .name { + color: var(--messageAttachmentFileInfo); + font-size: 14px; + line-height: 18px; + font-weight: 300; + margin-top: 2px; + overflow-x: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.message-attachment-generic > .text-part > .size { + color: var(--messageAttachmentFileInfo); + font-size: 11px; + line-height: 16px; + letter-spacing: 0.3px; + margin-top: 3px; +} +.module-message-detail { + margin-top: -20px; +} +.module-message-detail .bp3-callout { + max-height: 50vh; + overflow: auto; +} +.module-message-detail p { + white-space: pre-line; + user-select: text; +} +.module-message-detail__message-container { + padding-top: 20px; + padding-bottom: 20px; +} +.module-message-detail__message-container:after { + content: "."; + visibility: hidden; + display: block; + height: 0; + clear: both; +} +.module-message-detail__label { + font-weight: 300; + padding-right: 5px; +} +.module-message-detail__unix-timestamp { + color: #eeefef; +} +.module-message-detail__delete-button-container { + text-align: center; + margin-top: 10px; +} +.module-message-detail__delete-button { + background: none; + color: inherit; + border: none; + padding: 0; + font: inherit; + cursor: pointer; + outline: inherit; + background-color: #f44336; + color: #fff; + box-shadow: 0 0 10px -3px rgba(97, 97, 97, 0.7); + border-radius: 5px; + border: solid 1px #a4a6a9; + cursor: pointer; + margin: 1em auto; + padding: 1em; +} +.module-message-detail .message-content * { + background-color: lightgrey; + width: 100%; + resize: none; + padding: 1rem; +} +.message-list-and-composer { + width: 70%; + float: right; + display: grid; + grid-template-columns: auto; + height: calc(100vh - 50px); + margin-top: 50px; + background-image: var(--chatViewBgImgPath); + background-size: cover; + background-color: var(--chatViewBg); +} +.message-list-and-composer__message-list #message-list { + background: #dbdbdb; + position: absolute; + bottom: 0; + overflow: scroll; + max-height: 100%; + width: 100%; + padding: 0 0.5em; +} +.message-list-and-composer__message-list + #message-list::-webkit-scrollbar-track { + background: transparent; +} +.message-list-and-composer__message-list ul { + list-style: none; + min-width: 200px; +} +.message-list-and-composer__message-list ul li { + margin-bottom: 10px; + min-width: 200px; +} +.message-list-and-composer__message-list ul li::after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; +} +.message-list-and-composer__message-list ul li .info-message { + max-width: 550px; + font-size: 1rem; + padding: 2rem; + font-style: normal; + white-space: pre-wrap; + text-align: left; +} +.message { + position: relative; + display: inline-flex; + flex-direction: row; + align-items: stretch; +} +.message:hover .message-buttons { + opacity: 1; +} +.message > .author-avatar { + align-self: flex-end; + bottom: 0px; + position: static; + margin-right: 8px; + user-select: none; +} +.message > .author-avatar img { + height: 36px; + width: 36px; + border-radius: 18px; + object-fit: cover; + cursor: pointer; +} +.message > .author-avatar.default { + text-align: center; +} +.message > .author-avatar.default > .label { + user-select: none; + color: var(--avatarLabelColor); + top: -121px; + left: -10px; + border-radius: 50%; + width: 36px; + height: 36px; + font-size: 25px; + line-height: 36px; + cursor: pointer; +} +.message .message-buttons { + position: absolute; + top: 5px; + right: -4px; + display: inline-flex; + flex-direction: row; + align-items: center; + opacity: 0; + z-index: 10; + user-select: text; +} +.message .message-buttons .msg-button { + height: 24px; + width: 24px; + display: inline-block; + cursor: pointer; +} +.message .message-buttons .msg-button:hover { + background-color: var(--messageButtons); +} +.message .message-buttons .msg-button.download { + -webkit-mask: url("../images/download.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: var(--messageButtons); +} +.message .message-buttons .msg-button.reply { + display: none; + -webkit-mask: url("../images/reply.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: var(--messageButtons); + user-select: none; +} +.message .message-buttons .msg-button.menu { + -webkit-mask: url("../images/ellipsis.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: var(--messageButtons); + transform: rotate(90deg); + -webkit-mask-position-y: 4px; + user-select: none; +} +.message .msg-container { + position: relative; + display: inline-block; + border-radius: 16px; + padding-right: 12px; + padding-left: 12px; + padding-top: 10px; + padding-bottom: 10px; +} +.message .msg-container > .author { + display: inline-block; + max-width: 40vw; + font-size: 13px; + font-weight: 300; + line-height: 18px; + height: 18px; + overflow-x: hidden; + overflow-y: hidden; + white-space: nowrap; + text-overflow: ellipsis; + cursor: pointer; +} +.message .msg-container .msg-body.msg-body--clickable { + cursor: pointer; +} +.message .msg-container .msg-body > .text { + color: var(--messageText); + font-size: 14px; + line-height: 18px; + text-align: start; + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-word; + white-space: pre-wrap; + margin-right: 10px; +} +.message .msg-container .msg-body > .text a { + text-decoration: underline; + color: var(--messageTextLink); +} +.message .msg-container .msg-body > .text .double-line-break { + height: 28px; +} +.message .msg-container .msg-body > .text .line-break { + height: 14px; +} +.message .msg-container .msg-body > .text .line-break:last-child { + height: 0px; +} +.message .metadata { + margin-top: 10px; + margin-bottom: -7px; + float: right; +} +.message .module-message__img-attachment { + object-fit: cover; + width: auto; + max-width: 100%; + min-height: unset; +} +.message.incoming { + margin-left: 0; + margin-right: 32px; +} +.message.incoming .metadata:not(.with-image-no-caption) > .padlock-icon { + -webkit-mask: url("../images/padlock.svg") no-repeat center; + -webkit-mask-size: 125%; + background-color: var(--messagePadlockIncomming); +} +.message.incoming .metadata:not(.with-image-no-caption) > .location-icon { + -webkit-mask: url("../images/map-marker.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: var(--messagePadlockIncomming); +} +.message.incoming .metadata:not(.with-image-no-caption) > .date { + color: var(--messageMetadataIncomming); +} +.message.incoming .msg-container { + background-color: var(--messageIncommingBg); +} +.message.incoming .msg-container, +.message.incoming .msg-container .message-attachment-media { + border-bottom-left-radius: 1px; +} +.message.outgoing { + float: right; + margin-right: 0; + margin-left: 32px; +} +.message.outgoing .metadata > .date { + color: var(--messageOutgoingStatusColor); +} +.message.outgoing .metadata > .padlock-icon { + -webkit-mask: url("../images/padlock.svg") no-repeat center; + -webkit-mask-size: 125%; + background-color: var(--messagePadlockOutgoing); +} +.message.outgoing .metadata > .location-icon { + -webkit-mask: url("../images/map-marker.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: var(--messagePadlockOutgoing); +} +.message.outgoing .metadata > .status-icon.read, +.message.outgoing .metadata > .status-icon.delivered { + background-color: var(--messageOutgoingStatusColor); +} +.message.outgoing .msg-container { + background-color: var(--messageOutgoingBg); +} +.message.outgoing .msg-container, +.message.outgoing .msg-container .message-attachment-media { + border-bottom-right-radius: 1px; +} +.message.type-sticker .msg-container { + background-color: transparent !important; +} +.message.type-sticker .message-attachment-media { + background-color: transparent; +} +.message.type-sticker .message-attachment-media > .attachment-content { + margin-bottom: 20px; +} +.message.type-sticker .metadata { + float: right; + padding: 4px 10px 1px 10px; + margin-bottom: -7px; + background-color: #01010159; + border-radius: 4px; + color: black; + font-weight: bold; +} +.message.type-sticker .metadata > .date { + font-size: 11px; + color: white; +} +.message.type-sticker .metadata > .padlock-icon { + -webkit-mask: url("../images/padlock.svg") no-repeat center; + -webkit-mask-size: 125%; + background-color: #fff; +} +.message.type-sticker .metadata > .location-icon { + -webkit-mask: url("../images/map-marker.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: #fff; +} +.message.type-sticker .status-icon.read, +.message.type-sticker .status-icon.delivered { + background-color: white; +} +.message.type-sticker:hover .msg-button.menu { + background-color: white; +} +.message.type-sticker:hover .react-contextmenu-wrapper { + background-color: #2525258f; + border-radius: 4px; +} +.message.error.incoming .text { + font-style: italic; +} +.message.forwarded .forwarded-indicator { + font-weight: bold; + font-size: 0.9em; + margin-bottom: 3px; + opacity: 0.86; +} +.message.forwarded .message-attachment-media { + border-top-left-radius: 0; + border-top-right-radius: 0; + margin-top: 0; +} +.setupMessage .message .text { + color: var(--setupMessageText); +} +.hide-on-small { + display: initial; +} +@media (max-width: 800px) { + .hide-on-small { + display: none; + } +} +@media (min-width: 800px) and (max-width: 925px) { + .message { + max-width: 374px; + } + .message.incoming { + margin-right: auto; + } + .message.outgoing { + margin-left: auto; + } +} +@media (min-width: 926px) { + .message { + max-width: 66%; + } + .message.incoming { + margin-right: auto; + } + .message.outgoing { + margin-left: auto; + } +} +.metadata { + display: flex; + flex-direction: row; + align-items: center; + margin-top: 3px; + margin-bottom: -3px; +} +.metadata.with-image-no-caption { + position: absolute; + right: 5px; + bottom: 5px; + float: right; + padding: 4px 10px 1px 10px; + margin: 0; + background-color: #0000008f; + border-radius: 4px; + font-weight: bold; +} +.metadata.with-image-no-caption > .date { + color: white; +} +.metadata.with-image-no-caption > .padlock-icon { + -webkit-mask: url("../images/padlock.svg") no-repeat center; + -webkit-mask-size: 125%; + background-color: #fff; +} +.metadata.with-image-no-caption .status-icon.sending { + background-color: white; +} +.metadata > .status-icon { + margin-bottom: 2px; +} +.metadata > .username { + margin-right: 10px; +} +.metadata > .date { + font-size: 11.5px; + line-height: 16px; + letter-spacing: 0.3px; + color: var(--messageMetadataDate); + text-transform: uppercase; +} +.metadata > .spacer { + flex-grow: 1; +} +.metadata > .padlock-icon, +.metadata > .location-icon { + width: 12px; + height: 12px; + display: inline-block; + margin-right: 2px; + margin-bottom: 3px; +} +.metadata > .location-icon { + margin-bottom: 0; +} +@keyframes __status-icon--spinning { + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +.status-icon { + width: 18px; + height: 12px; + display: inline-block; + margin-left: 2px; +} +.status-icon.sending { + -webkit-mask: url("../images/sending.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: var(--messageStatusIconSending); + animation: __status-icon--spinning 4s linear infinite; + width: 12px; + margin-left: 8px; +} +.status-icon.delivered { + -webkit-mask: url("../images/sent.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: var(--messageStatusIcon); +} +.status-icon.read { + -webkit-mask: url("../images/read.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: var(--messageStatusIcon); +} +.status-icon.error { + -webkit-mask: url("../images/error.svg") no-repeat center; + -webkit-mask-size: 100%; + background-color: var(--errorColor); + width: 12px; + margin-left: 8px; +} diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 49c76e244..f51a86dd1 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -1,5 +1,6 @@ use std::path::Path; use std::str::FromStr; +use std::time::{SystemTime, UNIX_EPOCH}; use deltachat::chat::{self, Chat, ChatId, ChatVisibility}; use deltachat::chatlist::*; @@ -9,9 +10,9 @@ use deltachat::context::*; use deltachat::dc_receive_imf::*; use deltachat::dc_tools::*; use deltachat::error::Error; +use deltachat::export_chat::{export_chat, pack_exported_chat}; use deltachat::imex::*; use deltachat::job::*; -use deltachat::export_chat::export_chat; use deltachat::location; use deltachat::lot::LotState; use deltachat::message::{self, Message, MessageState, MsgId}; @@ -875,6 +876,18 @@ pub fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::Error> { let chat_id = ChatId::new(arg1.parse()?); let res = export_chat(context, chat_id); println!("{:?}", res); + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + let destination_raw = context.get_blobdir().join(format!( + "exported_{}_{}.zip", + chat_id.to_u32(), + timestamp + )); + let destination = destination_raw.to_str().unwrap(); + let pack_res = pack_exported_chat(context, res, destination); + println!("{:?} - destination: {}", pack_res, destination); } "msginfo" => { ensure!(!arg1.is_empty(), "Argument missing."); diff --git a/src/export_chat.rs b/src/export_chat.rs index e6bfa262a..9d44264d5 100644 --- a/src/export_chat.rs +++ b/src/export_chat.rs @@ -6,6 +6,10 @@ use crate::context::Context; use crate::error::Error; use crate::message::*; use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; +use std::path::Path; +use zip::write::FileOptions; #[derive(Debug)] pub struct ExportChatResult { @@ -20,7 +24,41 @@ struct ContactInfo { profile_img: Option, } -// pub fn packExportedChat(artifact:ExportChatResult) -> ? {} +pub fn pack_exported_chat( + context: &Context, + artifact: ExportChatResult, + filename: &str, +) -> zip::result::ZipResult<()> { + let path = std::path::Path::new(filename); + let file = std::fs::File::create(&path).unwrap(); + + let mut zip = zip::ZipWriter::new(file); + + zip.start_file("index.html", Default::default())?; + zip.write_all(artifact.html.as_bytes())?; + + zip.start_file("styles.css", Default::default())?; + zip.write_all(include_bytes!("../assets/exported-chat.css"))?; + + zip.add_directory("blobs/", Default::default())?; + + let options = FileOptions::default(); + for blob_name in artifact.referenced_blobs { + let path = context.get_blobdir().join(&blob_name); + + // println!("adding file {:?} as {:?} ...", path, &blob_name); + zip.start_file_from_path(Path::new(&format!("blobs/{}", &blob_name)), options)?; + let mut f = File::open(path)?; + + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer)?; + zip.write_all(&*buffer)?; + buffer.clear(); + } + + zip.finish()?; + Ok(()) +} pub fn export_chat(context: &Context, chat_id: ChatId) -> ExportChatResult { let mut blobs = Vec::new(); @@ -46,20 +84,28 @@ pub fn export_chat(context: &Context, chat_id: ChatId) -> ExportChatResult { chat_author_ids.dedup(); // chache information about the authors let mut chat_authors: HashMap = HashMap::new(); - chat_authors.insert(0, ContactInfo { - name: "Err: Contact not found".to_owned(), - initial: "#".to_owned(), - profile_img: None, - color: "grey".to_owned(), - }); + chat_authors.insert( + 0, + ContactInfo { + name: "Err: Contact not found".to_owned(), + initial: "#".to_owned(), + profile_img: None, + color: "grey".to_owned(), + }, + ); for author_id in chat_author_ids { let contact = Contact::get_by_id(context, author_id); if let Ok(c) = contact { let profile_img_path: String; if let Some(path) = c.get_profile_image(context) { - profile_img_path = path.file_name().unwrap_or_else(|| std::ffi::OsStr::new("")).to_str().unwrap().to_owned(); - // push referenced blobs (avatars) - blobs.push(profile_img_path.clone()); + profile_img_path = path + .file_name() + .unwrap_or_else(|| std::ffi::OsStr::new("")) + .to_str() + .unwrap() + .to_owned(); + // push referenced blobs (avatars) + blobs.push(profile_img_path.clone()); } else { profile_img_path = "".to_owned(); } @@ -79,14 +125,13 @@ pub fn export_chat(context: &Context, chat_id: ChatId) -> ExportChatResult { } // run message_to_html for each message and generate the html that way - let mut html_messages:Vec = Vec::new(); + let mut html_messages: Vec = Vec::new(); for message in messages { if let Ok(msg) = message { html_messages.push(message_to_html(&chat_authors, msg)); } else { - html_messages.push( - format!( - r#"
  • + html_messages.push(format!( + r#"
  • @@ -95,13 +140,13 @@ pub fn export_chat(context: &Context, chat_id: ChatId) -> ExportChatResult {
  • "#, - message.unwrap_err() - ) - ); + message.unwrap_err() + )); } } // todo chat image, chat name and so on.. + let chat = Chat::load_from_db(context, chat_id).unwrap(); // todo option to export locations as kml? @@ -109,7 +154,23 @@ pub fn export_chat(context: &Context, chat_id: ChatId) -> ExportChatResult { // (those can be linked from the messages, they are stored in msg_info/[msg-id].txt) ExportChatResult { - html: format!(r#"
      {}
    "#, html_messages.join("")), + html: format!( + "\ + \ + {}\ + \ + \ + \ +
    \ +
    \ +
      {}
    \ +
    \ +
    \ + \ + ", + chat.get_name(), + html_messages.join("") + ), referenced_blobs: blobs, } } @@ -127,21 +188,21 @@ fn message_to_html(author_cache: &HashMap, message: Message) - if let Some(profile_img) = &author.profile_img { format!( "
    \ - \ -
    ", + \ + ", author_name = author.name, author_avatar_src = profile_img ) } else { format!( "
    \ -
    \ - {initial}\ -
    \ -
    ", +
    \ + {initial}\ +
    \ + ", name = author.name, initial = author.initial, color = author.color @@ -155,26 +216,26 @@ fn message_to_html(author_cache: &HashMap, message: Message) - format!( "
  • \ -
    \ - {avatar}\ -
    \ - {author_name}\ -
    \ -
    \ - {content}\ -
    \ -
    \ - {encryption}\ - {relative_time}\ - \ -
    \ -
    \ -
    \ -
    \ -
  • ", +
    \ + {avatar}\ +
    \ + {author_name}\ +
    \ +
    \ + {content}\ +
    \ +
    \ + {encryption}\ + {relative_time}\ + \ +
    \ +
    \ +
    \ +
    \ + ", direction = match message.from_id == DC_CONTACT_ID_SELF { true => "outgoing", - false => "incomming", + false => "incoming", }, avatar = avatar, author_name = author.name,