{$post->getText()|noescape}
+
+ {var $width = ($GLOBALS["_bigWall"] ?? false) ? 550 : 320}
+ {if isset($GLOBALS["_nesAttGloCou"])}
+ {var $width = $width - 70 * $GLOBALS["_nesAttGloCou"]}
+ {/if}
+ {var $attachmentsLayout = $post->getChildrenWithLayout($width)}
+
+
-
+ {include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post"}
+
+
-
- {include "../attachment.xml", attachment => $attachment}
+
diff --git a/Web/Presenters/templates/components/post/oldpost.xml b/Web/Presenters/templates/components/post/oldpost.xml
index 9dadeaab..3223e6b7 100644
--- a/Web/Presenters/templates/components/post/oldpost.xml
+++ b/Web/Presenters/templates/components/post/oldpost.xml
@@ -65,8 +65,19 @@
{$post->getText()|noescape}
-
+
+ {include "../attachment.xml", attachment => $attachment, post => $post}
-
+ {var $width = ($GLOBALS["_bigWall"] ?? false) ? 550 : 320}
+ {if isset($GLOBALS["_nesAttGloCou"])}
+ {var $width = $width - 70 * $GLOBALS["_nesAttGloCou"]}
+ {/if}
+ {var $attachmentsLayout = $post->getChildrenWithLayout($width)}
+
+
+
+
+ {include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post"}
+
+
+
diff --git a/Web/Presenters/templates/components/textArea.xml b/Web/Presenters/templates/components/textArea.xml
index 939e5ad5..8727addf 100644
--- a/Web/Presenters/templates/components/textArea.xml
+++ b/Web/Presenters/templates/components/textArea.xml
@@ -1,5 +1,6 @@
{php if(!isset($GLOBALS["textAreaCtr"])) $GLOBALS["textAreaCtr"] = 10;}
{var $textAreaId = ($post ?? NULL) === NULL ? (++$GLOBALS["textAreaCtr"]) : $post->getId()}
+{var $textAreaId = ($custom_id ?? NULL) === NULL ? $textAreaId : $custom_id}
{include "../attachment.xml", attachment => $attachment}
+ >
+
+ for($i = 0; $i < sizeof($optimalConfiguration); $i++) {
+ $lineChunksNum = $optimalConfiguration[$i];
+ $lineThumbs = [];
+ for($j = 0; $j < $lineChunksNum; $j++)
+ $lineThumbs[] = array_shift($thumbsRemain);
+
+ $lineHeight = $optHeights[$i];
+ $totalHeight += $lineHeight;
+
+ $result->rowSizes[$i] = ceil($lineHeight);
+
+ $totalWidth = 0;
+ $row = [];
+ for($j = 0; $j < sizeof($lineThumbs); $j++) {
+ $thumbRatio = array_shift($ratiosRemain);
+ if($j == sizeof($lineThumbs) - 1)
+ $w = $maxWidth - $totalWidth;
+ else
+ $w = $thumbRatio * $lineHeight;
+
+ $totalWidth += ceil($w);
+ if($j < (sizeof($lineThumbs) - 1) && !in_array($totalWidth, $gridLineOffsets))
+ $gridLineOffsets[] = $totalWidth;
+
+ $tile = new ThumbTile(1, 1, $w, $lineHeight);
+ $result->tiles[$k++] = $row[] = $tile;
+ }
+
+ $rowTiles[] = $row;
+ }
+
+ sort($gridLineOffsets, SORT_NUMERIC);
+ $gridLineOffsets[] = $maxWidth;
+
+ $result->colSizes = [$gridLineOffsets[0]];
+ for($i = sizeof($gridLineOffsets) - 1; $i > 0; $i--)
+ $result->colSizes[$i] = $gridLineOffsets[$i] - $gridLineOffsets[$i - 1];
+
+ foreach($rowTiles as $row) {
+ $columnOffset = 0;
+ foreach($row as $tile) {
+ $startColumn = $columnOffset;
+ $width = 0;
+ $tile->colSpan = 0;
+ for($i = $startColumn; $i < sizeof($result->colSizes); $i++) {
+ $width += $result->colSizes[$i];
+ $tile->colSpan++;
+ if($width == $tile->width)
+ break;
+ }
+
+ $columnOffset += $tile->colSpan;
+ }
+ }
+
+ $result->height = ceil($totalHeight + $marginHeight * (sizeof($optHeights) - 1));
+ break;
+ }
+
+ return $result;
+ }
+}
diff --git a/Web/Util/Makima/MasonryLayout.php b/Web/Util/Makima/MasonryLayout.php
new file mode 100644
index 00000000..b23aa483
--- /dev/null
+++ b/Web/Util/Makima/MasonryLayout.php
@@ -0,0 +1,10 @@
+width, $this->height, $this->rowSpan, $this->colSpan] = [ceil($w), ceil($h), $rs, $cs];
+ }
+}
diff --git a/Web/routes.yml b/Web/routes.yml
index bc95f44a..dea8ddd5 100644
--- a/Web/routes.yml
+++ b/Web/routes.yml
@@ -371,6 +371,8 @@ routes:
handler: "About->humansTxt"
- url: "/dev"
handler: "About->dev"
+ - url: "/iapi/getPhotosFromPost/{num}_{num}"
+ handler: "InternalAPI->getPhotosFromPost"
- url: "/tour"
handler: "About->tour"
- url: "/{?shortCode}"
diff --git a/Web/static/css/main.css b/Web/static/css/main.css
index caa49283..47d67317 100644
--- a/Web/static/css/main.css
+++ b/Web/static/css/main.css
@@ -744,10 +744,14 @@ h4 {
line-height: 130%;
}
-.post-content .attachments_b {
+.post-content .attachments:first-of-type {
margin-top: 8px;
}
+.post-content .attachments_m .attachment {
+ width: 98%;
+}
+
.attachment .post {
width: 102%;
}
@@ -757,6 +761,12 @@ h4 {
image-rendering: -webkit-optimize-contrast;
}
+.post-content .media_makima {
+ width: calc(100% - 4px);
+ height: calc(100% - 4px);
+ object-fit: cover;
+}
+
.post-signature {
margin: 4px;
margin-bottom: 2px;
@@ -2279,6 +2289,124 @@ a.poll-retract-vote {
border-radius: 1px;
}
+.progress {
+ border: 1px solid #eee;
+ height: 15px;
+ background: linear-gradient(to bottom, #fefefe, #fafafa);
+}
+
+.progress .progress-bar {
+ background: url('progress.png');
+ background-repeat: repeat-x;
+ height: 15px;
+ animation-name: progress;
+ animation-duration: 1s;
+ animation-iteration-count: infinite;
+ animation-timing-function: linear;
+}
+
+@keyframes progress {
+ from {
+ background-position: 0;
+ }
+
+ to {
+ background-position: 20px;
+ }
+}
+
+.upload {
+ margin-top: 8px;
+}
+
+.upload .upload-item {
+ width: 75px;
+ height: 60px;
+ overflow: hidden;
+ display: inline-block;
+ margin-right: 3px;
+}
+
+.upload-item .upload-delete {
+ position: absolute;
+ background: rgba(0,0,0,0.5);
+ padding: 2px 5px;
+ text-decoration: none;
+ color: #fff;
+ font-size: 11px;
+ margin-left: 57px; /* мне лень переделывать :DDDD */
+ opacity: 0;
+ transition: 0.25s;
+}
+
+.upload-item:hover > .upload-delete {
+ opacity: 1;
+}
+
+.upload-item img {
+ width: 100%;
+ max-height: 60px;
+ object-fit: cover;
+ border-radius: 3px;
+}
+
+/* https://imgur.com/a/ihB3JZ4 */
+
+.ovk-photo-view-dimmer {
+ position: fixed;
+ left: 0px;
+ top: 0px;
+ right: 0px;
+ bottom: 0px;
+ overflow: auto;
+ padding-bottom: 20px;
+ z-index: 300;
+}
+
+.ovk-photo-view {
+ position: relative;
+ z-index: 999;
+ background: #fff;
+ width: 610px;
+ padding: 20px;
+ padding-top: 15px;
+ padding-bottom: 10px;
+ box-shadow: 0px 0px 3px 1px #222;
+ margin: 15px auto 0 auto;
+}
+
+.ovk-photo-details {
+ overflow: auto;
+}
+
+.photo_com_title {
+ font-weight: bold;
+ padding-bottom: 20px;
+}
+
+.photo_com_title div {
+ float: right;
+ font-weight: normal;
+}
+
+.ovk-photo-slide-left {
+ left: 0;
+ width: 35%;
+ height: 100%;
+ max-height: 60vh;
+ position: absolute;
+ cursor: pointer;
+}
+
+.ovk-photo-slide-right {
+ right: 0;
+ width: 35%;
+ height: 100%;
+ max-height: 60vh;
+ position: absolute;
+ cursor: pointer;
+}
+
.client_app > img {
top: 3px;
position: relative;
diff --git a/Web/static/js/al_wall.js b/Web/static/js/al_wall.js
index ef3d5dba..39ee2199 100644
--- a/Web/static/js/al_wall.js
+++ b/Web/static/js/al_wall.js
@@ -22,37 +22,14 @@ function trim(string) {
return newStr;
}
-function handleUpload(id) {
- console.warn("блять...");
-
- u("#post-buttons" + id + " .postFileSel").not("#" + this.id).each(input => input.value = null);
-
- var indicator = u("#post-buttons" + id + " .post-upload");
- var file = this.files[0];
- if(typeof file === "undefined") {
- indicator.attr("style", "display: none;");
- } else {
- u("span", indicator.nodes[0]).text(trim(file.name) + " (" + humanFileSize(file.size, false) + ")");
- indicator.attr("style", "display: block;");
- }
-
- document.querySelector("#post-buttons" + id + " #wallAttachmentMenu").classList.add("hidden");
-}
-
function initGraffiti(id) {
let canvas = null;
let msgbox = MessageBox(tr("draw_graffiti"), "", [tr("save"), tr("cancel")], [function() {
canvas.getImage({includeWatermark: false}).toBlob(blob => {
let fName = "Graffiti-" + Math.ceil(performance.now()).toString() + ".jpeg";
let image = new File([blob], fName, {type: "image/jpeg", lastModified: new Date().getTime()});
- let trans = new DataTransfer();
- trans.items.add(image);
- let fileSelect = document.querySelector("#post-buttons" + id + " input[name='_pic_attachment']");
- fileSelect.files = trans.files;
-
- u(fileSelect).trigger("change");
- u("#post-buttons" + id + " #write textarea").trigger("focusin");
+ fastUploadImage(id, image)
}, "image/jpeg", 0.92);
canvas.teardown();
@@ -75,6 +52,79 @@ function initGraffiti(id) {
});
}
+function fastUploadImage(textareaId, file) {
+ // uploading images
+
+ if(!file.type.startsWith('image/')) {
+ MessageBox(tr("error"), tr("only_images_accepted", escapeHtml(file.name)), [tr("ok")], [() => {Function.noop}])
+ return;
+ }
+
+ // 🤓🤓🤓
+ if(file.size > 5 * 1024 * 1024) {
+ MessageBox(tr("error"), tr("max_filesize", 5), [tr("ok")], [() => {Function.noop}])
+ return;
+ }
+
+ let imagesCount = document.querySelector("#post-buttons" + textareaId + " input[name='photos']").value.split(",").length
+
+ if(imagesCount > 10) {
+ MessageBox(tr("error"), tr("too_many_photos"), [tr("ok")], [() => {Function.noop}])
+ return
+ }
+
+ let xhr = new XMLHttpRequest
+ let data = new FormData
+
+ data.append("photo_0", file)
+ data.append("count", 1)
+ data.append("hash", u("meta[name=csrf]").attr("value"))
+
+ xhr.open("POST", "/photos/upload")
+
+ xhr.onloadstart = () => {
+ document.querySelector("#post-buttons"+textareaId+" .upload").insertAdjacentHTML("beforeend", ``)
+ }
+
+ xhr.onload = () => {
+ let response = JSON.parse(xhr.responseText)
+
+ appendImage(response, textareaId)
+ }
+
+ xhr.send(data)
+}
+
+// append image after uploading via /photos/upload
+function appendImage(response, textareaId) {
+ if(!response.success) {
+ MessageBox(tr("error"), (tr("error_uploading_photo") + response.flash.message), [tr("ok")], [() => {Function.noop}])
+ } else {
+ let form = document.querySelector("#post-buttons"+textareaId)
+ let photosInput = form.querySelector("input[name='photos']")
+ let photosIndicator = form.querySelector(".upload")
+
+ for(const phot of response.photos) {
+ let id = phot.owner + "_" + phot.vid
+
+ photosInput.value += (id + ",")
+
+ u(photosIndicator).append(u(`
+
+
+
- {_attachment}: (unknown)
+
{_poll}
@@ -56,7 +60,7 @@
{_comment_as_group}
-
+
@@ -73,7 +77,7 @@
{_attach}
-
+ canBeModifiedBy($thisUser)}data-club="{$club->getId()}"{/if}>
{_photo}
@@ -101,14 +105,11 @@
{if $graffiti}
diff --git a/Web/Util/Makima/Makima.php b/Web/Util/Makima/Makima.php
new file mode 100644
index 00000000..e31bfa42
--- /dev/null
+++ b/Web/Util/Makima/Makima.php
@@ -0,0 +1,305 @@
+photos = $photos;
+ }
+
+ private function getOrientation(Photo $photo, &$ratio): int
+ {
+ [$width, $height] = $photo->getDimensions();
+ $ratio = $width / $height;
+ if($ratio >= 1.2)
+ return Makima::ORIENT_WIDE;
+ else if($ratio >= 0.8)
+ return Makima::ORIENT_REGULAR;
+ else
+ return Makima::ORIENT_SLIM;
+ }
+
+ private function calculateMultiThumbsHeight(array $ratios, float $w, float $m): float
+ {
+ return ($w - (sizeof($ratios) - 1) * $m) / array_sum($ratios);
+ }
+
+ private function extractSubArr(array $arr, int $from, int $to): array
+ {
+ return array_slice($arr, $from, sizeof($arr) - $from - (sizeof($arr) - $to));
+ }
+
+ function computeMasonryLayout(float $maxWidth, float $maxHeight): MasonryLayout
+ {
+ $orients = [];
+ $ratios = [];
+ $count = sizeof($this->photos);
+ $result = new MasonryLayout;
+
+ foreach($this->photos as $photo) {
+ $orients[] = $this->getOrientation($photo, $ratio);
+ $ratios[] = $ratio;
+ }
+
+ $avgRatio = array_sum($ratios) / sizeof($ratios);
+ if($maxWidth < 0)
+ $maxWidth = $maxHeight = 510;
+
+ $maxRatio = $maxWidth / $maxHeight;
+ $marginWidth = $marginHeight = 2;
+
+ switch($count) {
+ case 2:
+ if(
+ $orients == [Makima::ORIENT_WIDE, Makima::ORIENT_WIDE] # two wide pics
+ && $avgRatio > (1.4 * $maxRatio) && abs($ratios[0] - $ratios[1]) < 0.2 # that can be positioned on top of each other
+ ) {
+ $computedHeight = ceil( min( $maxWidth / $ratios[0], min( $maxWidth / $ratios[1], ($maxHeight - $marginHeight) / 2 ) ) );
+
+ $result->colSizes = [1];
+ $result->rowSizes = [1, 1];
+ $result->width = ceil($maxWidth);
+ $result->height = $computedHeight;
+ $result->tiles = [new ThumbTile(1, 1, $maxWidth, $computedHeight), new ThumbTile(1, 1, $maxWidth, $computedHeight)];
+ } else if(
+ $orients == [Makima::ORIENT_WIDE, Makima::ORIENT_WIDE]
+ || $orients == [Makima::ORIENT_REGULAR, Makima::ORIENT_REGULAR] # two normal pics of same ratio
+ ) {
+ $computedWidth = ($maxWidth - $marginWidth) / 2;
+ $height = min( $computedWidth / $ratios[0], min( $computedWidth / $ratios[1], $maxHeight ) );
+
+ $result->colSizes = [1, 1];
+ $result->rowSizes = [1];
+ $result->width = ceil($maxWidth);
+ $result->height = ceil($height);
+ $result->tiles = [new ThumbTile(1, 1, $computedWidth, $height), new ThumbTile(1, 1, $computedWidth, $height)];
+ } else /* next to each other, different ratios */ {
+ $w0 = (
+ ($maxWidth - $marginWidth) / $ratios[1] / ( (1 / $ratios[0]) + (1 / $ratios[1]) )
+ );
+ $w1 = $maxWidth - $w0 - $marginWidth;
+ $h = min($maxHeight, min($w0 / $ratios[0], $w1 / $ratios[1]));
+
+ $result->colSizes = [ceil($w0), ceil($w1)];
+ $result->rowSizes = [1];
+ $result->width = ceil($w0 + $w1 + $marginWidth);
+ $result->height = ceil($h);
+ $result->tiles = [new ThumbTile(1, 1, $w0, $h), new ThumbTile(1, 1, $w1, $h)];
+ }
+ break;
+ case 3:
+ # Three wide photos, we will put two of them below and one on top
+ if($orients == [Makima::ORIENT_WIDE, Makima::ORIENT_WIDE, Makima::ORIENT_WIDE]) {
+ $hCover = min($maxWidth / $ratios[0], ($maxHeight - $marginHeight) * (2 / 3));
+ $w2 = ($maxWidth - $marginWidth) / 2;
+ $h = min($maxHeight - $hCover - $marginHeight, min($w2 / $ratios[1], $w2 / $ratios[2]));
+
+ $result->colSizes = [1, 1];
+ $result->rowSizes = [ceil($hCover), ceil($h)];
+ $result->width = ceil($maxWidth);
+ $result->height = ceil($marginHeight + $hCover + $h);
+ $result->tiles = [
+ new ThumbTile(2, 1, $maxWidth, $hCover),
+ new ThumbTile(1, 1, $w2, $h), new ThumbTile(1, 1, $w2, $h),
+ ];
+ } else /* Photos have different sizes or are not wide, so we will put one to left and two to the right */ {
+ $wCover = min($maxHeight * $ratios[0], ($maxWidth - $marginWidth) * (3 / 4));
+ $h1 = ($ratios[1] * ($maxHeight - $marginHeight) / ($ratios[2] + $ratios[1]));
+ $h0 = $maxHeight - $marginHeight - $h1;
+ $w = min($maxWidth - $marginWidth - $wCover, min($h1 * $ratios[2], $h0 * $ratios[1]));
+
+ $result->colSizes = [ceil($wCover), ceil($w)];
+ $result->rowSizes = [ceil($h0), ceil($h1)];
+ $result->width = ceil($w + $wCover + $marginWidth);
+ $result->height = ceil($maxHeight);
+ $result->tiles = [
+ new ThumbTile(1, 2, $wCover, $maxHeight), new ThumbTile(1, 1, $w, $h0),
+ new ThumbTile(1, 1, $w, $h1),
+ ];
+ }
+ break;
+ case 4:
+ # Four wide photos, we will put one to the top and rest below
+ if($orients == [Makima::ORIENT_WIDE, Makima::ORIENT_WIDE, Makima::ORIENT_WIDE, Makima::ORIENT_WIDE]) {
+ $hCover = min($maxWidth / $ratios[0], ($maxHeight - $marginHeight) / (2 / 3));
+ $h = ($maxWidth - 2 * $marginWidth) / (array_sum($ratios) - $ratios[0]);
+ $w0 = $h * $ratios[1];
+ $w1 = $h * $ratios[2];
+ $w2 = $h * $ratios[3];
+ $h = min($maxHeight - $marginHeight - $hCover, $h);
+
+ $result->colSizes = [ceil($w0), ceil($w1), ceil($w2)];
+ $result->rowSizes = [ceil($hCover), ceil($h)];
+ $result->width = ceil($maxWidth);
+ $result->height = ceil($hCover + $marginHeight + $h);
+ $result->tiles = [
+ new ThumbTile(3, 1, $maxWidth, $hCover),
+ new ThumbTile(1, 1, $w0, $h), new ThumbTile(1, 1, $w1, $h), new ThumbTile(1, 1, $w2, $h),
+ ];
+ } else /* Four photos, we will put one to the left and rest to the right */ {
+ $wCover = min($maxHeight * $ratios[0], ($maxWidth - $marginWidth) * (2 / 3));
+ $w = ($maxHeight - 2 * $marginHeight) / (1 / $ratios[1] + 1 / $ratios[2] + 1 / $ratios[3]);
+ $h0 = $w / $ratios[1];
+ $h1 = $w / $ratios[2];
+ $h2 = $w / $ratios[3] + $marginHeight;
+ $w = min($w, $maxWidth - $marginWidth - $wCover);
+
+ $result->colSizes = [ceil($wCover), ceil($w)];
+ $result->rowSizes = [ceil($h0), ceil($h1), ceil($h2)];
+ $result->width = ceil($wCover + $marginWidth + $w);
+ $result->height = ceil($maxHeight);
+ $result->tiles = [
+ new ThumbTile(1, 3, $wCover, $maxHeight), new ThumbTile(1, 1, $w, $h0),
+ new ThumbTile(1, 1, $w, $h1),
+ new ThumbTile(1, 1, $w, $h1),
+ ];
+ }
+ break;
+ default:
+ // как лопать пузырики
+ $ratiosCropped = [];
+ if($avgRatio > 1.1) {
+ foreach($ratios as $ratio)
+ $ratiosCropped[] = max($ratio, 1.0);
+ } else {
+ foreach($ratios as $ratio)
+ $ratiosCropped[] = min($ratio, 1.0);
+ }
+
+ $tries = [];
+
+ $firstLine;
+ $secondLine;
+ $thirdLine;
+
+ # Try one line:
+ $tries[$firstLine = $count] = [$this->calculateMultiThumbsHeight($ratiosCropped, $maxWidth, $marginWidth)];
+
+ # Try two lines:
+ for($firstLine = 1; $firstLine < ($count - 1); $firstLine++) {
+ $secondLine = $count - $firstLine;
+ $key = "$firstLine&$secondLine";
+ $tries[$key] = [
+ $this->calculateMultiThumbsHeight(array_slice($ratiosCropped, 0, $firstLine), $maxWidth, $marginWidth),
+ $this->calculateMultiThumbsHeight(array_slice($ratiosCropped, $firstLine), $maxWidth, $marginWidth),
+ ];
+ }
+
+ # Try three lines:
+ for($firstLine = 1; $firstLine < ($count - 2); $firstLine++) {
+ for($secondLine = 1; $secondLine < ($count - $firstLine - 1); $secondLine++) {
+ $thirdLine = $count - $firstLine - $secondLine;
+ $key = "$firstLine&$secondLine&$thirdLine";
+ $tries[$key] = [
+ $this->calculateMultiThumbsHeight(array_slice($ratiosCropped, 0, $firstLine), $maxWidth, $marginWidth),
+ $this->calculateMultiThumbsHeight($this->extractSubArr($ratiosCropped, $firstLine, $firstLine + $secondLine), $maxWidth, $marginWidth),
+ $this->calculateMultiThumbsHeight($this->extractSubArr($ratiosCropped, $firstLine + $secondLine, sizeof($ratiosCropped)), $maxWidth, $marginWidth),
+ ];
+ }
+ }
+
+ # Now let's find the most optimal configuration:
+ $optimalConfiguration = $optimalDifference = NULL;
+ foreach($tries as $config => $heights) {
+ $config = explode('&', (string) $config); # да да стринговые ключи пхп даже со стриктайпами автокастует к инту (см. 187)
+ $confH = $marginHeight * (sizeof($heights) - 1);
+ foreach($heights as $h)
+ $confH += $h;
+
+ $confDiff = abs($confH - $maxHeight);
+ if(sizeof($config) > 1)
+ if($config[0] > $config[1] || sizeof($config) >= 2 && $config[1] > $config[2])
+ $confDiff *= 1.1;
+
+ if(!$optimalConfiguration || $confDigff < $optimalDifference) {
+ $optimalConfiguration = $config;
+ $optimalDifference = $confDiff;
+ }
+ }
+
+ $thumbsRemain = $this->photos;
+ $ratiosRemain = $ratiosCropped;
+ $optHeights = $tries[implode('&', $optimalConfiguration)];
+ $k = 0;
+
+ $result->width = ceil($maxWidth);
+ $result->rowSizes = [sizeof($optHeights)];
+ $result->tiles = [];
+
+ $totalHeight = 0.0;
+ $gridLineOffsets = [];
+ $rowTiles = []; // vector
+ ×
+
+
+ `))
+
+ u(photosIndicator.querySelector(`.upload #aP[data-id='${id}'] .upload-delete`)).on("click", () => {
+ photosInput.value = photosInput.value.replace(id + ",", "")
+ u(form.querySelector(`.upload #aP[data-id='${id}']`)).remove()
+ })
+ }
+ }
+ u(`#post-buttons${textareaId} .upload #loader`).remove()
+}
+
u(".post-like-button").on("click", function(e) {
e.preventDefault();
@@ -97,11 +147,12 @@ u(".post-like-button").on("click", function(e) {
function setupWallPostInputHandlers(id) {
u("#wall-post-input" + id).on("paste", function(e) {
+ // Если вы находитесь на странице с постом с id 11, то копирование произойдёт джва раза.
+ // Оч ржачный баг, но вот как его исправить, я, если честно, не знаю.
+
if(e.clipboardData.files.length === 1) {
- var input = u("#post-buttons" + id + " input[name=_pic_attachment]").nodes[0];
- input.files = e.clipboardData.files;
-
- u(input).trigger("change");
+ fastUploadImage(id, e.clipboardData.files[0])
+ return;
}
});
@@ -116,6 +167,183 @@ function setupWallPostInputHandlers(id) {
// revert to original size if it is larger (possibly changed by user)
// textArea.style.height = (newHeight > originalHeight ? (newHeight + boost) : originalHeight) + "px";
});
+
+ u("#wall-post-input" + id).on("dragover", function(e) {
+ e.preventDefault()
+
+ // todo add animation
+ return;
+ });
+
+ $("#wall-post-input" + id).on("drop", function(e) {
+ e.originalEvent.dataTransfer.dropEffect = 'move';
+ fastUploadImage(id, e.originalEvent.dataTransfer.files[0])
+ return;
+ });
+}
+
+function OpenMiniature(e, photo, post, photo_id, type = "post") {
+ /*
+ костыли но смешные однако
+ */
+ e.preventDefault();
+
+ if(u(".ovk-photo-view").length > 0) u(".ovk-photo-view-dimmer").remove();
+
+ // Значения для переключения фоток
+
+ let json;
+
+ let imagesCount = 0;
+ let imagesIndex = 0;
+
+ let tempDetailsSection = [];
+
+ let dialog = u(
+ `
+
+
`);
+ u("body").addClass("dimmed").append(dialog);
+ document.querySelector("html").style.overflowY = "hidden"
+
+ let button = u("#ovk-photo-close");
+
+ button.on("click", function(e) {
+ let __closeDialog = () => {
+ u("body").removeClass("dimmed");
+ u(".ovk-photo-view-dimmer").remove();
+ document.querySelector("html").style.overflowY = "scroll"
+ };
+
+ __closeDialog();
+ });
+
+ function __reloadTitleBar() {
+ u("#photo_com_title_photos").last().innerHTML = imagesCount > 1 ? tr("photo_x_from_y", imagesIndex, imagesCount) : tr("photo");
+ }
+
+ function __loadDetails(photo_id, index) {
+ if(tempDetailsSection[index] == null) {
+ u(".ovk-photo-details").last().innerHTML = '';
+ ky("/photo" + photo_id, {
+ hooks: {
+ afterResponse: [
+ async (_request, _options, response) => {
+ let parser = new DOMParser();
+ let body = parser.parseFromString(await response.text(), "text/html");
+
+ let element = u(body.getElementsByClassName("ovk-photo-details")).last();
+
+ tempDetailsSection[index] = element.innerHTML;
+
+ if(index == imagesIndex) {
+ u(".ovk-photo-details").last().innerHTML = element.innerHTML;
+ }
+
+ document.querySelectorAll(".ovk-photo-details .bsdn").forEach(bsdnInitElement)
+ document.querySelectorAll(".ovk-photo-details script").forEach(scr => {
+ // stolen from #953
+ let newScr = document.createElement('script')
+
+ if(scr.src) {
+ newScr.src = scr.src
+ } else {
+ newScr.textContent = scr.textContent
+ }
+
+ document.querySelector(".ovk-photo-details").appendChild(newScr);
+ })
+ }
+ ]
+ }
+ });
+ } else {
+ u(".ovk-photo-details").last().innerHTML = tempDetailsSection[index];
+ }
+ }
+
+ function __slidePhoto(direction) {
+ /* direction = 1 - right
+ direction = 0 - left */
+ if(json == undefined) {
+ console.log("Да подожди ты. Куда торопишься?");
+ } else {
+ if(imagesIndex >= imagesCount && direction == 1) {
+ imagesIndex = 1;
+ } else if(imagesIndex <= 1 && direction == 0) {
+ imagesIndex = imagesCount;
+ } else if(direction == 1) {
+ imagesIndex++;
+ } else if(direction == 0) {
+ imagesIndex--;
+ }
+
+ let photoURL = json.body[imagesIndex - 1].url;
+
+ u("#ovk-photo-img").last().src = photoURL;
+ __reloadTitleBar();
+ __loadDetails(json.body[imagesIndex - 1].id, imagesIndex);
+ }
+ }
+
+ let slideLeft = u(".ovk-photo-slide-left");
+
+ slideLeft.on("click", (e) => {
+ __slidePhoto(0);
+ });
+
+ let slideRight = u(".ovk-photo-slide-right");
+
+ slideRight.on("click", (e) => {
+ __slidePhoto(1);
+ });
+
+ let data = new FormData()
+ data.append('parentType', type);
+ ky.post("/iapi/getPhotosFromPost/" + (type == "post" ? post : "1_"+post), {
+ hooks: {
+ afterResponse: [
+ async (_request, _options, response) => {
+ json = await response.json();
+
+ imagesCount = json.body.length;
+ imagesIndex = 0;
+ // Это всё придётся правда на 1 прибавлять
+
+ json.body.every(element => {
+ imagesIndex++;
+ if(element.id == photo_id) {
+ return false;
+ } else {
+ return true;
+ }
+ });
+
+ __reloadTitleBar();
+ __loadDetails(json.body[imagesIndex - 1].id, imagesIndex); }
+ ]
+ },
+ body: data
+ });
+
+ return u(".ovk-photo-view-dimmer");
}
u("#write > form").on("keydown", function(event) {
@@ -210,6 +438,7 @@ function addNote(textareaId, nid)
u("body").removeClass("dimmed");
u(".ovk-diag-cont").remove();
+ document.querySelector("html").style.overflowY = "scroll"
}
async function attachNote(id)
@@ -525,3 +754,210 @@ $(document).on("click", "#editPost", (e) => {
text.style.display = "block"
}
})
+
+// copypaste from videos picker
+$(document).on("click", "#photosAttachments", async (e) => {
+ let body = `
+
+
+
+
+ ${tr("upload_new_photo")}:
+
+
+
+
+
+
+
+
+ `
+
+ let form = e.currentTarget.closest("form")
+
+ MessageBox(tr("select_photo"), body, [tr("close")], [Function.noop]);
+
+ document.querySelector(".ovk-diag-body").style.padding = "0"
+ document.querySelector(".ovk-diag-cont").style.width = "630px"
+ document.querySelector(".ovk-diag-body").style.height = "335px"
+
+ async function insertPhotos(page, album = 0) {
+ u("#loader").remove()
+
+ let insertPlace = document.querySelector(".photosInsert .photosList")
+ document.querySelector(".photosInsert").insertAdjacentHTML("beforeend", ``)
+
+ let photos;
+
+ try {
+ photos = await API.Photos.getPhotos(page, Number(album))
+ } catch(e) {
+ document.querySelector(".photosInsert h4").innerHTML = tr("is_x_photos", -1)
+ insertPlace.innerHTML = "Invalid album"
+ console.error(e)
+ u("#loader").remove()
+ return;
+ }
+
+ document.querySelector(".photosInsert h4").innerHTML = tr("is_x_photos", photos.count)
+
+ let pagesCount = Math.ceil(Number(photos.count) / 24)
+ u("#loader").remove()
+
+ for(const photo of photos.items) {
+ let isAttached = (form.querySelector("input[name='photos']").value.includes(`${photo.owner_id}_${photo.id},`))
+
+ insertPlace.insertAdjacentHTML("beforeend", `
+ ${tr("is_x_photos", 0)}
+
+
+
+
+ `)
+ }
+
+ if(page < pagesCount) {
+ insertPlace.insertAdjacentHTML("beforeend", `
+
+ more...
+
`)
+ }
+ }
+
+ insertPhotos(1)
+
+ let albums = await API.Photos.getAlbums(Number(e.currentTarget.dataset.club ?? 0))
+
+ for(const alb of albums.items) {
+ let sel = document.querySelector(".ovk-diag-body #albumSelect")
+
+ sel.insertAdjacentHTML("beforeend", ``)
+ }
+
+ $(".photosInsert").on("click", "#showMorePhotos", (e) => {
+ u(e.currentTarget).remove()
+ insertPhotos(Number(e.currentTarget.dataset.page), document.querySelector(".topGrayBlock #albumSelect").value)
+ })
+
+ $(".topGrayBlock #albumSelect").on("change", (evv) => {
+ document.querySelector(".photosInsert .photosList").innerHTML = ""
+
+ insertPhotos(1, evv.currentTarget.value)
+ })
+
+ function insertAttachment(id) {
+ let photos = form.querySelector("input[name='photos']")
+
+ if(!photos.value.includes(id + ",")) {
+ if(photos.value.split(",").length > 10) {
+ NewNotification(tr("error"), tr("max_attached_photos"))
+ return false
+ }
+
+ form.querySelector("input[name='photos']").value += (id + ",")
+
+ console.info(id + " attached")
+ return true
+ } else {
+ form.querySelector("input[name='photos']").value = form.querySelector("input[name='photos']").value.replace(id + ",", "")
+
+ console.info(id + " detached")
+ return false
+ }
+ }
+
+ $(".photosList").on("click", ".album-photo", (ev) => {
+ ev.preventDefault()
+
+ if(!insertAttachment(ev.currentTarget.dataset.attachmentdata)) {
+ u(form.querySelector(`.upload #aP[data-id='${ev.currentTarget.dataset.attachmentdata}']`)).remove()
+ ev.currentTarget.querySelector("img").style.backgroundColor = "white"
+ } else {
+ ev.currentTarget.querySelector("img").style.backgroundColor = "#646464"
+ let id = ev.currentTarget.dataset.attachmentdata
+
+ u(form.querySelector(`.upload`)).append(u(`
+
+ ×
+
+
+ `));
+
+ u(`.upload #aP[data-id='${ev.currentTarget.dataset.attachmentdata}'] .upload-delete`).on("click", () => {
+ form.querySelector("input[name='photos']").value = form.querySelector("input[name='photos']").value.replace(id + ",", "")
+ u(form.querySelector(`.upload #aP[data-id='${ev.currentTarget.dataset.attachmentdata}']`)).remove()
+ })
+ }
+ })
+
+ u("#fastFotosUplod").on("change", (evn) => {
+ let xhr = new XMLHttpRequest()
+ xhr.open("POST", "/photos/upload")
+
+ let formdata = new FormData()
+ let iterator = 0
+
+ for(const fille of evn.currentTarget.files) {
+ if(!fille.type.startsWith('image/')) {
+ continue;
+ }
+
+ if(fille.size > 5 * 1024 * 1024) {
+ continue;
+ }
+
+ if(evn.currentTarget.files.length >= 10) {
+ NewNotification(tr("error"), tr("max_attached_photos"))
+ return;
+ }
+
+ formdata.append("photo_"+iterator, fille)
+ iterator += 1
+ }
+
+ xhr.onloadstart = () => {
+ evn.currentTarget.parentNode.insertAdjacentHTML("beforeend", ``)
+ }
+
+ xhr.onload = () => {
+ let result = JSON.parse(xhr.responseText)
+
+ u("#loader").remove()
+ if(result.success) {
+ for(const pht of result.photos) {
+ let id = pht.owner + "_" + pht.vid
+
+ if(!insertAttachment(id)) {
+ return
+ }
+
+ u(form.querySelector(`.upload`)).append(u(`
+
+ ×
+
+
+ `));
+
+ u(`.upload #aP[data-id='${pht.owner + "_" + pht.vid}'] .upload-delete`).on("click", () => {
+ form.querySelector("input[name='photos']").value = form.querySelector("input[name='photos']").value.replace(id + ",", "")
+ u(form.querySelector(`.upload #aP[data-id='${id}']`)).remove()
+ })
+ }
+
+ u("body").removeClass("dimmed");
+ u(".ovk-diag-cont").remove();
+ document.querySelector("html").style.overflowY = "scroll"
+ } else {
+ // todo: https://vk.com/wall-32295218_78593
+ alert(result.flash.message)
+ }
+ }
+
+ formdata.append("hash", u("meta[name=csrf]").attr("value"))
+ formdata.append("count", iterator)
+
+ xhr.send(formdata)
+ })
+})
diff --git a/Web/static/js/messagebox.js b/Web/static/js/messagebox.js
index 368311dd..e56c720f 100644
--- a/Web/static/js/messagebox.js
+++ b/Web/static/js/messagebox.js
@@ -20,9 +20,12 @@ function MessageBox(title, body, buttons, callbacks) {
button.on("click", function(e) {
let __closeDialog = () => {
- u("body").removeClass("dimmed");
+ if(document.querySelector(".ovk-photo-view-dimmer") == null) {
+ u("body").removeClass("dimmed");
+ document.querySelector("html").style.overflowY = "scroll"
+ }
+
u(".ovk-diag-cont").remove();
- document.querySelector("html").style.overflowY = "scroll"
};
Reflect.apply(callbacks[callback], {
diff --git a/Web/static/js/openvk.cls.js b/Web/static/js/openvk.cls.js
index 22144cfc..b131bfa0 100644
--- a/Web/static/js/openvk.cls.js
+++ b/Web/static/js/openvk.cls.js
@@ -68,7 +68,7 @@ function toggleMenu(id) {
}
document.addEventListener("DOMContentLoaded", function() { //BEGIN
- u("#_photoDelete").on("click", function(e) {
+ $(document).on("click", "#_photoDelete", function(e) {
var formHtml = "";
diff --git a/locales/en.strings b/locales/en.strings
index 22cee453..b1d211d1 100644
--- a/locales/en.strings
+++ b/locales/en.strings
@@ -347,6 +347,7 @@
"albums" = "Albums";
"album" = "Album";
"photos" = "photos";
+"photo" = "Photo";
"create_album" = "Create album";
"edit_album" = "Edit album";
"edit_photo" = "Edit photo";
@@ -412,6 +413,20 @@
"tip" = "Tip";
"tip_ctrl" = "to select multiple photos at once, hold down the Ctrl key when selecting files in Windows or the CMD key in Mac OS.";
"album_poster" = "Album poster";
+"select_photo" = "Select photos";
+"upload_new_photo" = "Upload new photo";
+
+"is_x_photos_zero" = "Just zero photos.";
+"is_x_photos_one" = "Just one photo.";
+"is_x_photos_few" = "Just $1 photos.";
+"is_x_photos_many" = "Just $1 photos.";
+"is_x_photos_other" = "Just $1 photos.";
+
+"all_photos" = "All photos";
+"error_uploading_photo" = "Error when uploading photo. Error text: ";
+"too_many_photos" = "Too many photos.";
+
+"photo_x_from_y" = "Photo $1 from $2";
/* Notes */
@@ -697,6 +712,7 @@
"selecting_video" = "Selecting videos";
"upload_new_video" = "Upload new video";
"max_attached_videos" = "Max is 10 videos";
+"max_attached_photos" = "Max is 10 photos";
"no_videos" = "You don't have uploaded videos.";
"no_videos_results" = "No results.";
diff --git a/locales/ru.strings b/locales/ru.strings
index 2dfadb8f..49871399 100644
--- a/locales/ru.strings
+++ b/locales/ru.strings
@@ -329,6 +329,7 @@
"album" = "Альбом";
"albums" = "Альбомы";
"photos" = "фотографий";
+"photo" = "Фотография";
"create_album" = "Создать альбом";
"edit_album" = "Редактировать альбом";
"edit_photo" = "Изменить фотографию";
@@ -394,6 +395,20 @@
"tip" = "Подсказка";
"tip_ctrl" = "для того, чтобы выбрать сразу несколько фотографий, удерживайте клавишу Ctrl при выборе файлов в ОС Windows или клавишу CMD в Mac OS.";
"album_poster" = "Обложка альбома";
+"select_photo" = "Выберите фотографию";
+"upload_new_photo" = "Загрузить новую фотографию";
+
+"is_x_photos_zero" = "Всего ноль фотографий.";
+"is_x_photos_one" = "Всего одна фотография.";
+"is_x_photos_few" = "Всего $1 фотографии.";
+"is_x_photos_many" = "Всего $1 фотографий.";
+"is_x_photos_other" = "Всего $1 фотографий.";
+
+"all_photos" = "Все фотографии";
+"error_uploading_photo" = "Не удалось загрузить фотографию. Текст ошибки: ";
+"too_many_photos" = "Слишком много фотографий.";
+
+"photo_x_from_y" = "Фотография $1 из $2";
/* Notes */
@@ -656,6 +671,7 @@
"selecting_video" = "Выбор видеозаписей";
"upload_new_video" = "Загрузить новое видео";
"max_attached_videos" = "Максимум 10 видеозаписей";
+"max_attached_photos" = "Максимум 10 фотографий";
"no_videos" = "У вас нет видео.";
"no_videos_results" = "Нет результатов.";