aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArno Richter <oelna@oelna.de>2022-12-21 15:05:28 +0100
committerArno Richter <oelna@oelna.de>2022-12-21 15:05:28 +0100
commit482fd7adee5e9e0990bf5904ed7d754d315de649 (patch)
tree7523a6f3482b6cb024624310f21fa7eeb05e9866
parent057cace8b32e6c3d105695b517eae262071601f4 (diff)
downloadmicroblog-482fd7adee5e9e0990bf5904ed7d754d315de649.tar.gz
microblog-482fd7adee5e9e0990bf5904ed7d754d315de649.tar.bz2
microblog-482fd7adee5e9e0990bf5904ed7d754d315de649.zip
first attempt at image attachments!
-rw-r--r--.htaccess2
-rw-r--r--css/microblog.css107
-rw-r--r--favicon.icobin0 -> 318 bytes
-rw-r--r--files/2022/12/10-c067078.pngbin0 -> 94005 bytes
-rw-r--r--files/2022/12/12-da0864c.jpgbin0 -> 72022 bytes
-rw-r--r--files/2022/12/13-1612d57.jpgbin0 -> 49606 bytes
-rw-r--r--files/2022/12/13-8b2031d.jpgbin0 -> 4243895 bytes
-rw-r--r--files/2022/12/13-eae0c26.gifbin0 -> 2044842 bytes
-rw-r--r--files/2022/12/6-1612d57.jpgbin0 -> 49606 bytes
-rw-r--r--files/2022/12/6-426ace2.gifbin0 -> 15975 bytes
-rw-r--r--files/2022/12/6-da0864c.jpgbin0 -> 72022 bytes
-rw-r--r--files/2022/12/6-eae0c26.gifbin0 -> 2044842 bytes
-rw-r--r--files/2022/12/7-435f1c5.jpgbin0 -> 146075 bytes
-rw-r--r--files/2022/12/7-597815d.jpgbin0 -> 63967 bytes
-rw-r--r--files/2022/12/7-5e1af8d.jpgbin0 -> 18883 bytes
-rw-r--r--files/2022/12/7-98e0953.pngbin0 -> 8103 bytes
-rw-r--r--files/2022/12/7-da0864c.jpgbin0 -> 72022 bytes
-rw-r--r--files/2022/12/8-20572ca.jpgbin0 -> 157923 bytes
-rw-r--r--files/2022/12/8-5e1af8d.jpgbin0 -> 18883 bytes
-rw-r--r--files/2022/12/8-da0864c.jpgbin0 -> 72022 bytes
-rw-r--r--files/2022/12/81-5e1af8d.jpgbin0 -> 18883 bytes
-rw-r--r--files/2022/12/81-da0864c.jpgbin0 -> 72022 bytes
-rw-r--r--files/2022/12/82-506df72.jpgbin0 -> 3020458 bytes
-rw-r--r--files/2022/12/83-5e1af8d.jpgbin0 -> 18883 bytes
-rw-r--r--files/2022/12/83-da0864c.jpgbin0 -> 72022 bytes
-rw-r--r--files/2022/12/84-5e1af8d.jpgbin0 -> 18883 bytes
-rw-r--r--files/2022/12/85-5e1af8d.jpgbin0 -> 18883 bytes
-rw-r--r--files/2022/12/85-da0864c.jpgbin0 -> 72022 bytes
-rw-r--r--files/2022/12/86-da0864c.jpgbin0 -> 72022 bytes
-rw-r--r--files/2022/12/87-da0864c.jpgbin0 -> 72022 bytes
-rw-r--r--files/2022/12/88-5e1af8d.jpgbin0 -> 18883 bytes
-rw-r--r--files/2022/12/89-5e1af8d.jpgbin0 -> 18883 bytes
-rw-r--r--files/2022/12/9-5e1af8d.jpgbin0 -> 18883 bytes
-rw-r--r--files/2022/12/9-da0864c.jpgbin0 -> 72022 bytes
-rw-r--r--index.php57
-rw-r--r--js/microblog.js193
-rw-r--r--js/squircle.js146
-rw-r--r--lib/database.php48
-rw-r--r--lib/functions.php268
-rw-r--r--lib/xmlrpc.php4
-rw-r--r--microblog.js21
-rw-r--r--snippets/header.snippet.php11
-rw-r--r--templates/postform.inc.php19
-rw-r--r--templates/single.inc.php89
-rw-r--r--templates/timeline.inc.php30
45 files changed, 929 insertions, 66 deletions
diff --git a/.htaccess b/.htaccess
index 64b0c70..66aa61a 100644
--- a/.htaccess
+++ b/.htaccess
@@ -19,8 +19,6 @@ RewriteEngine On
RewriteBase /microblog
# friendly URLs
-RewriteRule ^rsd/?$ lib/rsd.xml.php [L]
-RewriteRule ^xmlrpc/?(.*)$ lib/xmlrpc.php?/$1 [L]
RewriteRule ^feed/json/?$ feed/feed.json [L]
RewriteRule ^feed/atom/?$ feed/feed.xml [L]
diff --git a/css/microblog.css b/css/microblog.css
index 1bc41be..e6a657a 100644
--- a/css/microblog.css
+++ b/css/microblog.css
@@ -14,6 +14,12 @@ html {
color: var(--text-color);
}
+img {
+ display: block;
+ max-width: 100%;
+ height: auto;
+}
+
.wrap {
width: min(95%, 40rem);
margin: 2rem auto;
@@ -37,6 +43,10 @@ html {
background: coral;
}
+.hidden {
+ display: none !important;
+}
+
nav.main ul {
display: flex;
margin-bottom: 2rem;
@@ -191,6 +201,13 @@ form.edit,
outline: none;
}
+:is(.postform, .edit) .post-nav {
+ width: 100%;
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+}
+
:is(.postform, .edit) input[type="submit"],
.login input[type="submit"] {
-webkit-appearance: none;
@@ -209,10 +226,86 @@ form.edit,
}
:is(.postform, .edit) #count {
- float: left;
color: var(--background-color);
}
+:is(.postform, .edit) #post-droparea {
+ border: 0.15rem dashed rgba(0,0,0,0.2);
+ color: rgba(0,0,0,0.35);
+ padding: 0.25rem;
+ cursor: pointer;
+}
+
+:is(.postform, .edit) #post-droparea.drag,
+:is(.postform, .edit) #post-droparea:is(:hover, :focus) {
+ background-color: var(--primary-color);
+ color: #fff;
+ border: 0.15rem solid var(--primary-color);
+}
+
+:is(.postform, .edit) #post-attachments-label {
+ display: flex;
+ border: 0.15rem dashed rgba(0,0,0,0.4);
+ color: rgba(0,0,0,0.4);
+ padding: 0.25rem;
+ cursor: pointer;
+ position: relative;
+ align-self: stretch;
+ align-items: center;
+}
+
+:is(.postform, .edit) #post-attachments {
+ /* cover the entire label, for drag and drop */
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ opacity: 0;
+}
+
+:is(.postform, .edit) #post-attachments-list {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ flex-wrap: nowrap;
+ padding-inline-end: 1rem;
+ align-self: stretch;
+ justify-content: center;
+}
+
+:is(.postform, .edit) #post-attachments-list li + li {
+ margin-block-start: 0.25em;
+ border-top: 1px solid rgba(0,0,0,0.2);
+ padding-block-start: 0.25em;
+}
+
+:is(.postform, .edit) #post-attachments-list img.file-preview {
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: 1ch;
+ width: 1.75rem;
+ height: 1.75rem;
+ outline: 0px solid #f0f;
+ object-fit: cover;
+ background: #fff;
+}
+
+:is(.postform, .edit) #post-attachments-list input[type="checkbox"] {
+ -webkit-appearance: checkbox;
+ appearance: checkbox;
+}
+
+:is(.timeline, .single) .post-attachments {
+ grid-column-start: span 6;
+ margin-block-start: 1rem;
+}
+
+:is(.timeline, .single) .post-attachments li + li {
+ margin-block-start: 0.5rem;
+}
+
:is(.postform, .edit) .message,
.login .message {
background-color: #87b26c;
@@ -264,3 +357,15 @@ footer li a {
font-weight: bold;
margin-bottom: 0.5rem;
}
+
+/*
+@supports (background: paint(id)) {
+ input[type="submit"] {
+ background: paint(squircle) !important;
+ --squircle-radius: 8px;
+ --squircle-fill: var(--primary-color);
+
+ border-radius: 0;
+ }
+}
+*/
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000..550d600
--- /dev/null
+++ b/favicon.ico
Binary files differ
diff --git a/files/2022/12/10-c067078.png b/files/2022/12/10-c067078.png
new file mode 100644
index 0000000..621b565
--- /dev/null
+++ b/files/2022/12/10-c067078.png
Binary files differ
diff --git a/files/2022/12/12-da0864c.jpg b/files/2022/12/12-da0864c.jpg
new file mode 100644
index 0000000..b4195d3
--- /dev/null
+++ b/files/2022/12/12-da0864c.jpg
Binary files differ
diff --git a/files/2022/12/13-1612d57.jpg b/files/2022/12/13-1612d57.jpg
new file mode 100644
index 0000000..0913630
--- /dev/null
+++ b/files/2022/12/13-1612d57.jpg
Binary files differ
diff --git a/files/2022/12/13-8b2031d.jpg b/files/2022/12/13-8b2031d.jpg
new file mode 100644
index 0000000..b2071b9
--- /dev/null
+++ b/files/2022/12/13-8b2031d.jpg
Binary files differ
diff --git a/files/2022/12/13-eae0c26.gif b/files/2022/12/13-eae0c26.gif
new file mode 100644
index 0000000..8e66b5c
--- /dev/null
+++ b/files/2022/12/13-eae0c26.gif
Binary files differ
diff --git a/files/2022/12/6-1612d57.jpg b/files/2022/12/6-1612d57.jpg
new file mode 100644
index 0000000..0913630
--- /dev/null
+++ b/files/2022/12/6-1612d57.jpg
Binary files differ
diff --git a/files/2022/12/6-426ace2.gif b/files/2022/12/6-426ace2.gif
new file mode 100644
index 0000000..4f0665e
--- /dev/null
+++ b/files/2022/12/6-426ace2.gif
Binary files differ
diff --git a/files/2022/12/6-da0864c.jpg b/files/2022/12/6-da0864c.jpg
new file mode 100644
index 0000000..b4195d3
--- /dev/null
+++ b/files/2022/12/6-da0864c.jpg
Binary files differ
diff --git a/files/2022/12/6-eae0c26.gif b/files/2022/12/6-eae0c26.gif
new file mode 100644
index 0000000..8e66b5c
--- /dev/null
+++ b/files/2022/12/6-eae0c26.gif
Binary files differ
diff --git a/files/2022/12/7-435f1c5.jpg b/files/2022/12/7-435f1c5.jpg
new file mode 100644
index 0000000..9a52ca4
--- /dev/null
+++ b/files/2022/12/7-435f1c5.jpg
Binary files differ
diff --git a/files/2022/12/7-597815d.jpg b/files/2022/12/7-597815d.jpg
new file mode 100644
index 0000000..22a7820
--- /dev/null
+++ b/files/2022/12/7-597815d.jpg
Binary files differ
diff --git a/files/2022/12/7-5e1af8d.jpg b/files/2022/12/7-5e1af8d.jpg
new file mode 100644
index 0000000..a564f0e
--- /dev/null
+++ b/files/2022/12/7-5e1af8d.jpg
Binary files differ
diff --git a/files/2022/12/7-98e0953.png b/files/2022/12/7-98e0953.png
new file mode 100644
index 0000000..ea839ff
--- /dev/null
+++ b/files/2022/12/7-98e0953.png
Binary files differ
diff --git a/files/2022/12/7-da0864c.jpg b/files/2022/12/7-da0864c.jpg
new file mode 100644
index 0000000..b4195d3
--- /dev/null
+++ b/files/2022/12/7-da0864c.jpg
Binary files differ
diff --git a/files/2022/12/8-20572ca.jpg b/files/2022/12/8-20572ca.jpg
new file mode 100644
index 0000000..d93a2d6
--- /dev/null
+++ b/files/2022/12/8-20572ca.jpg
Binary files differ
diff --git a/files/2022/12/8-5e1af8d.jpg b/files/2022/12/8-5e1af8d.jpg
new file mode 100644
index 0000000..a564f0e
--- /dev/null
+++ b/files/2022/12/8-5e1af8d.jpg
Binary files differ
diff --git a/files/2022/12/8-da0864c.jpg b/files/2022/12/8-da0864c.jpg
new file mode 100644
index 0000000..b4195d3
--- /dev/null
+++ b/files/2022/12/8-da0864c.jpg
Binary files differ
diff --git a/files/2022/12/81-5e1af8d.jpg b/files/2022/12/81-5e1af8d.jpg
new file mode 100644
index 0000000..a564f0e
--- /dev/null
+++ b/files/2022/12/81-5e1af8d.jpg
Binary files differ
diff --git a/files/2022/12/81-da0864c.jpg b/files/2022/12/81-da0864c.jpg
new file mode 100644
index 0000000..b4195d3
--- /dev/null
+++ b/files/2022/12/81-da0864c.jpg
Binary files differ
diff --git a/files/2022/12/82-506df72.jpg b/files/2022/12/82-506df72.jpg
new file mode 100644
index 0000000..d0b1bfe
--- /dev/null
+++ b/files/2022/12/82-506df72.jpg
Binary files differ
diff --git a/files/2022/12/83-5e1af8d.jpg b/files/2022/12/83-5e1af8d.jpg
new file mode 100644
index 0000000..a564f0e
--- /dev/null
+++ b/files/2022/12/83-5e1af8d.jpg
Binary files differ
diff --git a/files/2022/12/83-da0864c.jpg b/files/2022/12/83-da0864c.jpg
new file mode 100644
index 0000000..b4195d3
--- /dev/null
+++ b/files/2022/12/83-da0864c.jpg
Binary files differ
diff --git a/files/2022/12/84-5e1af8d.jpg b/files/2022/12/84-5e1af8d.jpg
new file mode 100644
index 0000000..a564f0e
--- /dev/null
+++ b/files/2022/12/84-5e1af8d.jpg
Binary files differ
diff --git a/files/2022/12/85-5e1af8d.jpg b/files/2022/12/85-5e1af8d.jpg
new file mode 100644
index 0000000..a564f0e
--- /dev/null
+++ b/files/2022/12/85-5e1af8d.jpg
Binary files differ
diff --git a/files/2022/12/85-da0864c.jpg b/files/2022/12/85-da0864c.jpg
new file mode 100644
index 0000000..b4195d3
--- /dev/null
+++ b/files/2022/12/85-da0864c.jpg
Binary files differ
diff --git a/files/2022/12/86-da0864c.jpg b/files/2022/12/86-da0864c.jpg
new file mode 100644
index 0000000..b4195d3
--- /dev/null
+++ b/files/2022/12/86-da0864c.jpg
Binary files differ
diff --git a/files/2022/12/87-da0864c.jpg b/files/2022/12/87-da0864c.jpg
new file mode 100644
index 0000000..b4195d3
--- /dev/null
+++ b/files/2022/12/87-da0864c.jpg
Binary files differ
diff --git a/files/2022/12/88-5e1af8d.jpg b/files/2022/12/88-5e1af8d.jpg
new file mode 100644
index 0000000..a564f0e
--- /dev/null
+++ b/files/2022/12/88-5e1af8d.jpg
Binary files differ
diff --git a/files/2022/12/89-5e1af8d.jpg b/files/2022/12/89-5e1af8d.jpg
new file mode 100644
index 0000000..a564f0e
--- /dev/null
+++ b/files/2022/12/89-5e1af8d.jpg
Binary files differ
diff --git a/files/2022/12/9-5e1af8d.jpg b/files/2022/12/9-5e1af8d.jpg
new file mode 100644
index 0000000..a564f0e
--- /dev/null
+++ b/files/2022/12/9-5e1af8d.jpg
Binary files differ
diff --git a/files/2022/12/9-da0864c.jpg b/files/2022/12/9-da0864c.jpg
new file mode 100644
index 0000000..b4195d3
--- /dev/null
+++ b/files/2022/12/9-da0864c.jpg
Binary files differ
diff --git a/index.php b/index.php
index b2a0855..e82cf27 100644
--- a/index.php
+++ b/index.php
@@ -13,29 +13,40 @@
$template = 'single';
require_once(ROOT.DS.'templates'.DS.'single.inc.php');
- } elseif(mb_strtolower(path(0)) === 'login') {
- $template = 'login';
- require_once(ROOT.DS.'templates'.DS.'loginform.inc.php');
-
- } elseif(mb_strtolower(path(0)) === 'logout') {
- $domain = ($_SERVER['HTTP_HOST'] != 'localhost') ? $_SERVER['HTTP_HOST'] : false;
- setcookie('microblog_login', '', time()-3600, '/', $domain, false);
- unset($_COOKIE['microblog_login']);
-
- header('Location: '.$config['url']);
- die();
-
- } elseif(mb_strtolower(path(0)) === 'new') {
- $template = 'postform';
- require_once(ROOT.DS.'templates'.DS.'postform.inc.php');
-
} else {
- // redirect everything else to the homepage
- if(!empty(path(0)) && path(0) != 'page') {
- header('Location: '.$config['url']);
- die();
+ $page = mb_strtolower(path(0));
+
+ switch($page) {
+ case 'login':
+ $template = 'login';
+ require_once(ROOT.DS.'templates'.DS.'loginform.inc.php');
+ break;
+ case 'logout':
+ $domain = ($_SERVER['HTTP_HOST'] != 'localhost') ? $_SERVER['HTTP_HOST'] : false;
+ setcookie('microblog_login', '', time()-3600, '/', $domain, false);
+ unset($_COOKIE['microblog_login']);
+
+ header('Location: '.$config['url']);
+ break;
+ case 'new':
+ $template = 'postform';
+ require_once(ROOT.DS.'templates'.DS.'postform.inc.php');
+ break;
+ case 'rsd':
+ require_once(ROOT.DS.'lib'.DS.'rsd.xml.php');
+ break;
+ case 'xmlrpc':
+ require_once(ROOT.DS.'lib'.DS.'xmlrpc.php');
+ break;
+ default:
+ // redirect everything else to the homepage
+ if(!empty(path(0)) && path(0) != 'page') {
+ header('Location: '.$config['url']);
+ die();
+ }
+
+ // show the homepage
+ require_once(ROOT.DS.'templates'.DS.'timeline.inc.php');
+ break;
}
-
- // show the homepage
- require_once(ROOT.DS.'templates'.DS.'timeline.inc.php');
}
diff --git a/js/microblog.js b/js/microblog.js
new file mode 100644
index 0000000..f7963e3
--- /dev/null
+++ b/js/microblog.js
@@ -0,0 +1,193 @@
+'use strict';
+
+document.documentElement.classList.remove('no-js');
+
+const textarea = document.querySelector('textarea[name="content"]');
+const charCount = document.querySelector('#count');
+
+if (textarea) {
+ const maxCount = parseInt(textarea.getAttribute('maxlength'));
+
+ if (textarea.value.length > 0) {
+ const textLength = [...textarea.value].length;
+ charCount.textContent = maxCount - textLength;
+ } else {
+ charCount.textContent = maxCount;
+ }
+
+ textarea.addEventListener('input', function () {
+ const textLength = [...this.value].length;
+
+ charCount.textContent = maxCount - textLength;
+ }, false);
+}
+
+const postForm = document.querySelector('#post-new-form');
+let useDragDrop = (!!window.FileReader && 'draggable' in document.createElement('div'));
+useDragDrop = true;
+if (postForm) {
+ const droparea = postForm.querySelector('#post-droparea');
+ const attachmentsInput = postForm.querySelector('#post-attachments');
+ const data = {
+ 'attachments': []
+ };
+
+
+ if (droparea && attachmentsInput) {
+ if (useDragDrop) {
+ console.log('init with modern file attachments');
+
+ const list = postForm.querySelector('#post-attachments-list');
+ list.addEventListener('click', function (e) {
+ e.preventDefault();
+
+ // remove attachment
+ if (e.target.nodeName.toLowerCase() == 'li') {
+ const filename = e.target.textContent;
+
+ data.attachments = data.attachments.filter(function (ele) {
+ return ele.name !== filename;
+ });
+
+ e.target.remove();
+ }
+ });
+
+ droparea.classList.remove('hidden');
+ document.querySelector('#post-attachments-label').classList.add('hidden');
+
+ droparea.ondragover = droparea.ondragenter = function (e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ e.dataTransfer.dropEffect = 'copy';
+ e.target.classList.add('drag');
+ };
+
+ droparea.ondragleave = function (e) {
+ e.target.classList.remove('drag');
+ };
+
+ droparea.onclick = function (e) {
+ e.preventDefault();
+
+ // make a virtual file upload
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.setAttribute('multiple', '');
+ input.setAttribute('accept', 'image/*'); // only images for now
+
+ input.onchange = e => {
+ processSelectedFiles(e.target.files);
+ }
+
+ input.click();
+ };
+
+ function processSelectedFiles(files) {
+ if (!files || files.length < 1) return;
+
+ for (const file of files) {
+ const found = data.attachments.find(ele => ele.name === file.name);
+ if(found) continue; // skip existing attachments
+
+ data.attachments.push({
+ 'name': file.name,
+ // todo: maybe some better form of dupe detection here?
+ 'file': file
+ });
+
+ const li = document.createElement('li');
+ li.textContent = file.name;
+
+ const reader = new FileReader();
+
+ if(file.type.startsWith('image/')) {
+ reader.onload = function (e) {
+ var dataURL = e.target.result;
+
+ const preview = document.createElement('img');
+ preview.classList.add('file-preview');
+ preview.setAttribute('src', dataURL);
+
+ li.prepend(preview);
+ };
+ reader.onerror = function (e) {
+ console.log('An error occurred during file input: '+e.target.error.code);
+ };
+
+ reader.readAsDataURL(file);
+ }
+
+ list.append(li);
+ }
+ }
+
+ droparea.ondrop = function (e) {
+ if (e.dataTransfer) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ processSelectedFiles(e.dataTransfer.files);
+ }
+
+ e.target.classList.remove('drag');
+ };
+
+ postForm.addEventListener('submit', async function (e) {
+ e.preventDefault();
+
+ const postFormData = new FormData();
+
+ postFormData.append('content', postForm.querySelector('[name="content"]').value);
+
+ for (const attachment of data.attachments) {
+ postFormData.append('attachments[]', attachment.file);
+ }
+
+ /*
+ for (const pair of postFormData.entries()) {
+ console.log(`${pair[0]}, ${pair[1]}`);
+ }
+ */
+
+ const response = await fetch(postForm.getAttribute('action'), {
+ body: postFormData,
+ method: 'POST'
+ });
+
+ if (response.ok && response.status == 200) {
+ const txt = await response.text();
+ // console.log(response, txt);
+ window.location.href = postForm.dataset.redirect;
+ } else {
+ console.warn('error during post submission!', response);
+ }
+ });
+ } else {
+ // use the native file input dialog
+ // but enhanced
+ if (attachmentsInput) {
+ console.log('init with classic file attachments');
+
+ attachmentsInput.addEventListener('change', function (e) {
+ console.log(e.target.files);
+
+ const list = postForm.querySelector('#post-attachments-list');
+ list.replaceChildren();
+
+ for (const file of e.target.files) {
+ const li = document.createElement('li');
+ li.textContent = file.name;
+ list.append(li);
+ }
+ });
+ }
+ }
+ }
+}
+
+// better rounded corners
+if ('paintWorklet' in CSS) {
+ // CSS.paintWorklet.addModule('./js/squircle.js');
+}
diff --git a/js/squircle.js b/js/squircle.js
new file mode 100644
index 0000000..063a383
--- /dev/null
+++ b/js/squircle.js
@@ -0,0 +1,146 @@
+const drawSquircle = (ctx, geom, radius, smooth, lineWidth, color) => {
+ const defaultFill = color;
+ const lineWidthOffset = lineWidth / 2;
+ // OPEN LEFT-TOP CORNER
+ ctx.beginPath();
+ ctx.lineTo(radius, lineWidthOffset);
+ // TOP-RIGHT CORNER
+ ctx.lineTo(geom.width - radius, lineWidthOffset);
+ ctx.bezierCurveTo(
+ geom.width - radius / smooth,
+ lineWidthOffset, // first bezier point
+ geom.width - lineWidthOffset,
+ radius / smooth, // second bezier point
+ geom.width - lineWidthOffset,
+ radius // last connect point
+ );
+ // BOTTOM-RIGHT CORNER
+ ctx.lineTo(geom.width - lineWidthOffset, geom.height - radius);
+ ctx.bezierCurveTo(
+ geom.width - lineWidthOffset,
+ geom.height - radius / smooth, // first bezier point
+ geom.width - radius / smooth,
+ geom.height - lineWidthOffset, // second bezier point
+ geom.width - radius,
+ geom.height - lineWidthOffset // last connect point
+ );
+ // BOTTOM-LEFT CORNER
+ ctx.lineTo(radius, geom.height - lineWidthOffset);
+ ctx.bezierCurveTo(
+ radius / smooth,
+ geom.height - lineWidthOffset, // first bezier point
+ lineWidthOffset,
+ geom.height - radius / smooth, // second bezier point
+ lineWidthOffset,
+ geom.height - radius // last connect point
+ );
+ // CLOSE LEFT-TOP CORNER
+ ctx.lineTo(lineWidthOffset, radius);
+ ctx.bezierCurveTo(
+ lineWidthOffset,
+ radius / smooth, // first bezier point
+ radius / smooth,
+ lineWidthOffset, // second bezier point
+ radius,
+ lineWidthOffset // last connect point
+ );
+ ctx.closePath();
+
+ if (lineWidth) {
+ // console.log(lineWidth);
+ ctx.strokeStyle = defaultFill;
+ ctx.lineWidth = lineWidth;
+ ctx.stroke();
+ } else {
+ ctx.fillStyle = defaultFill;
+ ctx.fill();
+ }
+};
+
+if (typeof registerPaint !== "undefined") {
+ class SquircleClass {
+ static get contextOptions() {
+ return { alpha: true };
+ }
+ static get inputProperties() {
+ return [
+ "--squircle-radius",
+ "--squircle-smooth",
+ "--squircle-outline",
+ "--squircle-fill",
+ "--squircle-ratio",
+ ];
+ }
+
+ paint(ctx, geom, properties) {
+ const customRatio = properties.get("--squircle-ratio");
+ const smoothRatio = 10;
+ const distanceRatio = parseFloat(customRatio)
+ ? parseFloat(customRatio)
+ : 1.8;
+ const squircleSmooth = parseFloat(
+ properties.get("--squircle-smooth") * smoothRatio
+ );
+ const squircleRadius =
+ parseInt(properties.get("--squircle-radius"), 10) * distanceRatio;
+ const squrcleOutline = parseFloat(
+ properties.get("--squircle-outline"),
+ 10
+ );
+ const squrcleColor = properties
+ .get("--squircle-fill")
+ .toString()
+ .replace(/\s/g, "");
+
+ const isSmooth = () => {
+ if (typeof properties.get("--squircle-smooth")[0] !== "undefined") {
+ if (squircleSmooth === 0) {
+ return 1;
+ }
+ return squircleSmooth;
+ } else {
+ return 10;
+ }
+ };
+
+ const isOutline = () => {
+ if (squrcleOutline) {
+ return squrcleOutline;
+ } else {
+ return 0;
+ }
+ };
+
+ const isColor = () => {
+ if (squrcleColor) {
+ return squrcleColor;
+ } else {
+ return "#f45";
+ }
+ };
+
+ if (squircleRadius < geom.width / 2 && squircleRadius < geom.height / 2) {
+ drawSquircle(
+ ctx,
+ geom,
+ squircleRadius,
+ isSmooth(),
+ isOutline(),
+ isColor()
+ );
+ } else {
+ drawSquircle(
+ ctx,
+ geom,
+ Math.min(geom.width / 2, geom.height / 2),
+ isSmooth(),
+ isOutline(),
+ isColor()
+ );
+ }
+ }
+ }
+
+ // eslint-disable-next-line no-undef
+ registerPaint("squircle", SquircleClass);
+} \ No newline at end of file
diff --git a/lib/database.php b/lib/database.php
index 5774d95..4e5e0cd 100644
--- a/lib/database.php
+++ b/lib/database.php
@@ -15,11 +15,12 @@ try {
// first time setup
if($config['db_version'] == 0) {
try {
- $db->exec("CREATE TABLE IF NOT EXISTS `posts` (
- `id` integer PRIMARY KEY NOT NULL,
+ $db->exec("PRAGMA `user_version` = 1;
+ CREATE TABLE IF NOT EXISTS `posts` (
+ `id` INTEGER PRIMARY KEY NOT NULL,
`post_content` TEXT,
`post_timestamp` INTEGER
- ); PRAGMA `user_version` = 1;");
+ );");
$config['db_version'] = 1;
} catch(PDOException $e) {
print 'Exception : '.$e->getMessage();
@@ -30,7 +31,7 @@ if($config['db_version'] == 0) {
// upgrade database to v2
if($config['db_version'] == 1) {
try {
- $db->exec("PRAGMA user_version = 2;
+ $db->exec("PRAGMA `user_version` = 2;
ALTER TABLE `posts` ADD `post_thread` INTEGER;
ALTER TABLE `posts` ADD `post_edited` INTEGER;
ALTER TABLE `posts` ADD `post_deleted` INTEGER;
@@ -45,7 +46,7 @@ if($config['db_version'] == 1) {
// upgrade database to v3
if($config['db_version'] == 2) {
try {
- $db->exec("PRAGMA user_version = 3;
+ $db->exec("PRAGMA `user_version` = 3;
ALTER TABLE `posts` ADD `post_guid` TEXT;
");
$config['db_version'] = 3;
@@ -55,5 +56,42 @@ if($config['db_version'] == 2) {
}
}
+// upgrade database to v4
+if($config['db_version'] == 3) {
+ try {
+ $db->exec("PRAGMA `user_version` = 4;
+ CREATE TABLE `files` (
+ `id` INTEGER PRIMARY KEY NOT NULL,
+ `file_filename` TEXT NOT NULL,
+ `file_extension` TEXT,
+ `file_original` TEXT NOT NULL,
+ `file_mime_type` TEXT,
+ `file_size` INTEGER,
+ `file_hash` TEXT UNIQUE,
+ `file_hash_algo` TEXT,
+ `file_meta` TEXT DEFAULT '{}',
+ `file_dir` TEXT,
+ `file_subdir` TEXT,
+ `file_timestamp` INTEGER,
+ `file_deleted` INTEGER
+ );
+ CREATE TABLE `file_to_post` (
+ `file_id` INTEGER NOT NULL,
+ `post_id` INTEGER NOT NULL,
+ `deleted` INTEGER,
+ UNIQUE(`file_id`, `post_id`) ON CONFLICT IGNORE
+ );
+ CREATE INDEX `posts_timestamp` ON posts (`post_timestamp`);
+ CREATE INDEX `files_original` ON files (`file_original`);
+ CREATE INDEX `link_deleted` ON file_to_post (`deleted`);
+ CREATE UNIQUE INDEX `files_hashes` ON files (`file_hash`);
+ ");
+ $config['db_version'] = 4;
+ } catch(PDOException $e) {
+ print 'Exception : '.$e->getMessage();
+ die('cannot upgrade database table to v4!');
+ }
+}
+
// debug: get a list of post table columns
// var_dump($db->query("PRAGMA table_info(`posts`)")->fetchAll(PDO::FETCH_COLUMN, 1));
diff --git a/lib/functions.php b/lib/functions.php
index 7046eb5..e606230 100644
--- a/lib/functions.php
+++ b/lib/functions.php
@@ -98,6 +98,33 @@ function db_select_post($id=0) {
return (!empty($row)) ? $row : false;
}
+function db_get_attached_files($post_id, $include_deleted=false) {
+ global $db;
+ if(empty($db)) return false;
+
+ $rows = [];
+
+ if($include_deleted) {
+ $sql = 'SELECT f.* FROM files f LEFT JOIN file_to_post p WHERE f.id = p.file_id AND p.post_id = :post_id ORDER BY f.file_timestamp ASC';
+ } else {
+ $sql = 'SELECT f.* FROM files f LEFT JOIN file_to_post p WHERE f.id = p.file_id AND p.post_id = :post_id AND p.deleted IS NULL ORDER BY f.file_timestamp ASC';
+ }
+
+ try {
+ $statement = $db->prepare($sql);
+ $statement->bindValue(':post_id', $post_id, PDO::PARAM_INT);
+
+ $statement->execute();
+
+ $rows = $statement->fetchAll(PDO::FETCH_ASSOC);
+ } catch(PDOException $e) {
+ // print 'Exception : '.$e->getMessage();
+ return false;
+ }
+
+ return (!empty($rows)) ? $rows : false;
+}
+
function db_select_posts($from, $amount=10, $sort='desc', $offset=0) {
global $db;
if(empty($db)) return false;
@@ -126,6 +153,225 @@ function db_posts_count() {
return (int) $row['posts_count'];
}
+function convert_files_array($input_array) {
+ $file_array = [];
+ $file_count = count($input_array['name']);
+ $file_keys = array_keys($input_array);
+
+ for ($i=0; $i<$file_count; $i++) {
+ foreach ($file_keys as $key) {
+ $file_array[$i][$key] = $input_array[$key][$i];
+ }
+ }
+
+ return $file_array;
+}
+
+function attach_uploaded_files($files=[], $post_id=null) {
+ if(empty($files['tmp_name'][0])) return false;
+
+ $files = convert_files_array($files);
+ //var_dump($files);exit();
+
+ foreach($files as $file) {
+
+ if (!isset($file['error']) || is_array($file['error'])) {
+ // invalid parameters
+ // var_dump('bad file info');exit();
+ continue; // skip this file
+ }
+
+ if($file['size'] > 20000000) {
+ // Exceeded filesize limit.
+ // var_dump('invalid file size');exit();
+ continue;
+ }
+
+ $mime = mime_content_type($file['tmp_name']);
+ if (false === $ext = array_search(
+ $mime,
+ array(
+ 'jpg' => 'image/jpeg',
+ 'png' => 'image/png',
+ 'gif' => 'image/gif',
+ 'avif' => 'image/avif',
+ 'webp' => 'image/webp',
+ // todo: video
+ 'txt' => 'text/plain',
+ 'md' => 'text/markdown',
+ ),
+ true
+ )) {
+ // Invalid file format.
+ // var_dump('invalid format');exit();
+ continue;
+ }
+
+ save_file($file['name'], $ext, $file['tmp_name'], $post_id, $mime);
+ }
+}
+
+function detatch_files($file_ids=[], $post_id=null) {
+ global $db;
+ if(empty($db)) return false;
+ if(empty($file_ids)) return false;
+ if(!$post_id) return false;
+
+ $file_id = null;
+
+ try {
+ $statement = $db->prepare('UPDATE file_to_post SET deleted = :delete_time WHERE file_id = :file_id AND post_id = :post_id');
+
+ $statement->bindParam(':file_id', $file_id, PDO::PARAM_INT);
+ $statement->bindParam(':post_id', $post_id, PDO::PARAM_INT);
+ $statement->bindValue(':delete_time', time(), PDO::PARAM_INT);
+
+ foreach ($file_ids as $id) {
+ $file_id = $id;
+ $statement->execute();
+ }
+
+ } catch(PDOException $e) {
+ // print 'Exception : '.$e->getMessage();
+ return false;
+ }
+
+ return true;
+}
+
+function db_select_file($query, $method='id') {
+ global $db;
+ if(empty($db)) return false;
+ if($id === 0) return false;
+
+ switch ($method) {
+ case 'hash':
+ $statement = $db->prepare('SELECT * FROM files WHERE file_hash = :q LIMIT 1');
+ $statement->bindValue(':q', $query, PDO::PARAM_STR);
+ break;
+ case 'filename':
+ $statement = $db->prepare('SELECT * FROM files WHERE file_filename = :q LIMIT 1');
+ $statement->bindValue(':q', $query, PDO::PARAM_STR);
+ break;
+ default:
+ $statement = $db->prepare('SELECT * FROM files WHERE id = :q LIMIT 1');
+ $statement->bindValue(':q', $query, PDO::PARAM_INT);
+ break;
+ }
+
+ $statement->execute();
+ $row = $statement->fetch(PDO::FETCH_ASSOC);
+
+ return (!empty($row)) ? $row : false;
+}
+
+function db_link_file($file_id, $post_id) {
+ global $db;
+ if(empty($db)) return false;
+
+ try {
+ $statement = $db->prepare('INSERT INTO file_to_post (file_id, post_id) VALUES (:file_id, :post_id)');
+
+ $statement->bindValue(':file_id', $file_id, PDO::PARAM_INT);
+ $statement->bindValue(':post_id', $post_id, PDO::PARAM_INT);
+
+ $statement->execute();
+ } catch(PDOException $e) {
+ // print 'Exception : '.$e->getMessage();
+ return false;
+ }
+
+ return true;
+}
+
+function save_file($filename, $extension, $tmp_file, $post_id, $mime='') {
+ global $db;
+ if(empty($db)) return false;
+
+ $files_dir = ROOT.DS.'files';
+ $hash_algo = 'sha1';
+
+ $insert = [
+ 'file_extension' => $extension,
+ 'file_original' => $filename,
+ 'file_mime_type' => $mime,
+ 'file_size' => filesize($tmp_file),
+ 'file_hash' => hash_file($hash_algo, $tmp_file),
+ 'file_hash_algo' => $hash_algo,
+ 'file_meta' => '{}',
+ 'file_dir' => date('Y'),
+ 'file_subdir' => date('m'),
+ 'file_timestamp' => time()
+ ];
+
+ if(!is_dir($files_dir.DS.$insert['file_dir'])) {
+ mkdir($files_dir.DS.$insert['file_dir'], 0755);
+ }
+
+ if(!is_dir($files_dir.DS.$insert['file_dir'].DS.$insert['file_subdir'])) {
+ mkdir($files_dir.DS.$insert['file_dir'].DS.$insert['file_subdir'], 0755);
+ }
+
+ $insert['file_filename'] = $post_id . '-' . substr($insert['file_hash'], 0, 7);
+ $path = $files_dir.DS.$insert['file_dir'].DS.$insert['file_subdir'];
+
+ if(move_uploaded_file($tmp_file, $path.DS.$insert['file_filename'] .'.'. $insert['file_extension'])) {
+ // add to database
+
+ // check if file exists already
+ $existing = db_select_file($insert['file_hash'], 'hash');
+
+ if(!empty($existing)) {
+ // just link existing file
+ if(db_link_file($existing['id'], $post_id)) {
+ return $existing['id'];
+ } else {
+ return false;
+ }
+ } else {
+ // insert new
+ try {
+ $statement = $db->prepare('INSERT INTO files (file_filename, file_extension, file_original, file_mime_type, file_size, file_hash, file_hash_algo, file_meta, file_dir, file_subdir, file_timestamp) VALUES (:file_filename, :file_extension, :file_original, :file_mime_type, :file_size, :file_hash, :file_hash_algo, :file_meta, :file_dir, :file_subdir, :file_timestamp)');
+
+ $statement->bindValue(':file_filename', $insert['file_filename'], PDO::PARAM_STR);
+ $statement->bindValue(':file_extension', $insert['file_extension'], PDO::PARAM_STR);
+ $statement->bindValue(':file_original', $insert['file_original'], PDO::PARAM_STR);
+ $statement->bindValue(':file_mime_type', $insert['file_mime_type'], PDO::PARAM_STR);
+ $statement->bindValue(':file_size', $insert['file_size'], PDO::PARAM_INT);
+ $statement->bindValue(':file_hash', $insert['file_hash'], PDO::PARAM_STR);
+ $statement->bindValue(':file_hash_algo', $insert['file_hash_algo'], PDO::PARAM_STR);
+ $statement->bindValue(':file_meta', $insert['file_meta'], PDO::PARAM_STR);
+ $statement->bindValue(':file_dir', $insert['file_dir'], PDO::PARAM_STR);
+ $statement->bindValue(':file_subdir', $insert['file_subdir'], PDO::PARAM_STR);
+ $statement->bindValue(':file_timestamp', $insert['file_timestamp'], PDO::PARAM_INT);
+
+ $statement->execute();
+
+ // todo: check this?
+ db_link_file($db->lastInsertId(), $post_id);
+
+ return $db->lastInsertId();
+ } catch(PDOException $e) {
+ print 'Exception : '.$e->getMessage();
+ return false;
+ }
+ }
+ }
+
+ return false;
+}
+
+function get_file_path($file) {
+ $url = '';
+
+ $url .= 'files/';
+ $url .= $file['file_dir'] . '/';
+ $url .= $file['file_subdir'] . '/';
+ $url .= $file['file_filename'] . '.' . $file['file_extension'];
+
+ return $url;
+}
+
/* function that pings the official micro.blog endpoint for feed refreshes */
function ping_microblog() {
global $config;
@@ -178,13 +424,33 @@ function rebuild_json_feed($posts=[]) {
foreach($posts as $post) {
+ $attachments = db_get_attached_files($post['id']);
+ $post_attachments = [];
+ if(!empty($attachments)) {
+ foreach ($attachments as $a) {
+ $post_attachments[] = [
+ 'url' => $config['url'] .'/'. get_file_path($a),
+ 'mime_type' => $a['file_mime_type'],
+ 'size_in_bytes' => $a['file_size']
+ ];
+ }
+ }
+
+ $post_images = array_filter($post_attachments, function($v) {
+ return strpos($v['mime_type'], 'image') === 0;
+ });
+
$feed['items'][] = array(
'id' => ($post['post_guid'] ? 'urn:uuid:'.$post['post_guid'] : $config['url'].'/'.$post['id']),
'url' => $config['url'].'/'.$post['id'],
'title' => '',
'content_html' => $post['post_content'],
- 'date_published' => gmdate('Y-m-d\TH:i:s\Z', $post['post_timestamp'])
+ 'date_published' => gmdate('Y-m-d\TH:i:s\Z', $post['post_timestamp']),
+ 'image' => !empty($post_images) ? $post_images[0]['url'] : '',
+ 'attachments' => $post_attachments
);
+
+
}
if(file_put_contents($filename, json_encode($feed, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES))) {
diff --git a/lib/xmlrpc.php b/lib/xmlrpc.php
index 51e1be2..985d0f1 100644
--- a/lib/xmlrpc.php
+++ b/lib/xmlrpc.php
@@ -3,11 +3,9 @@
$request_xml = file_get_contents("php://input");
// check prerequisites
-if(!function_exists('xmlrpc_server_create')) { exit('No XML-RPC support detected!'); }
+if(!$config['xmlrpc']) { exit('No XML-RPC support detected!'); }
if(empty($request_xml)) { exit('XML-RPC server accepts POST requests only.'); }
-// load config
-require_once(__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'config.php');
$logfile = ROOT.DS.'log.txt';
if(!function_exists('str_starts_with')) {
diff --git a/microblog.js b/microblog.js
deleted file mode 100644
index 03e8d11..0000000
--- a/microblog.js
+++ /dev/null
@@ -1,21 +0,0 @@
-'use strict';
-
-const textarea = document.querySelector('textarea[name="content"]');
-const charCount = document.querySelector('#count');
-
-if (textarea) {
- const maxCount = parseInt(textarea.getAttribute('maxlength'));
-
- if (textarea.value.length > 0) {
- const textLength = [...textarea.value].length;
- charCount.textContent = maxCount - textLength;
- } else {
- charCount.textContent = maxCount;
- }
-
- textarea.addEventListener('input', function () {
- const textLength = [...this.value].length;
-
- charCount.textContent = maxCount - textLength;
- }, false);
-}
diff --git a/snippets/header.snippet.php b/snippets/header.snippet.php
index d3270d8..cc0becb 100644
--- a/snippets/header.snippet.php
+++ b/snippets/header.snippet.php
@@ -9,7 +9,7 @@
header('Content-Type: text/html; charset=utf-8');
?><!DOCTYPE html>
-<html lang="<?= $config['language'] ?>" class="<?= $template ?>">
+<html lang="<?= $config['language'] ?>" class="no-js <?= $template ?>">
<head>
<meta charset="utf-8" />
@@ -20,8 +20,15 @@
<link rel="alternate" type="application/json" title="JSON Feed" href="<?= $config['url'] ?>/feed/json" />
<link rel="alternate" type="application/atom+xml" title="Atom Feed" href="<?= $config['url'] ?>/feed/atom" />
<?php if($config['xmlrpc']): ?><link rel="EditURI" type="application/rsd+xml" title="RSD" href="<?= $config['url'] ?>/rsd" /><?php endif; ?>
+
+ <link rel="authorization_endpoint" href="https://micro.blog/indieauth/auth" />
+ <link rel="token_endpoint" href="https://micro.blog/indieauth/token" />
+
+ <?php if(!empty($config['microblog_account'])): ?>
+ <link href="https://micro.blog/<?= ltrim($config['microblog_account'], '@') ?>" rel="me" />
+ <?php endif; ?>
<link rel="stylesheet" href="<?= $config['url'] ?>/css/<?= $css ?>.css" />
- <script src="<?= $config['url'] ?>/microblog.js" type="module" defer></script>
+ <script src="<?= $config['url'] ?>/js/microblog.js" type="module" defer></script>
</head>
diff --git a/templates/postform.inc.php b/templates/postform.inc.php
index 149028b..df7566c 100644
--- a/templates/postform.inc.php
+++ b/templates/postform.inc.php
@@ -10,7 +10,7 @@
$message = array();
if(!empty($_POST['content'])) {
-
+
$id = db_insert($_POST['content'], NOW);
if($id > 0) {
@@ -19,6 +19,11 @@
'message' => 'Successfully posted status #'.$id
);
+ // handle files
+ if(!empty($_FILES['attachments'])) {
+ attach_uploaded_files($_FILES['attachments'], $id);
+ }
+
rebuild_feeds();
if($config['ping'] == true) ping_microblog();
if($config['crosspost_to_twitter'] == true) {
@@ -43,10 +48,16 @@
<?php if(isset($message['status']) && isset($message['message'])): ?>
<p class="message <?= $message['status'] ?>"><?= $message['message'] ?></p>
<?php endif; ?>
- <form action="" method="post">
+ <form action="" method="post" enctype="multipart/form-data" id="post-new-form" data-redirect="<?= $config['url'] ?>">
<textarea name="content" maxlength="<?= $config['max_characters'] ?>"></textarea>
- <p id="count"><?= $config['max_characters'] ?></p>
- <input type="submit" name="" value="Post" />
+
+ <div class="post-nav">
+ <label id="post-attachments-label">Add Files<input type="file" multiple="multiple" name="attachments[]" id="post-attachments" accept="image/*" /></label>
+ <div id="post-droparea" class="hidden">Add Files</div>
+ <ul id="post-attachments-list"></ul>
+ <p id="count"><?= $config['max_characters'] ?></p>
+ <input type="submit" name="" value="Post" />
+ </div>
</form>
</div>
<?php require(ROOT.DS.'snippets'.DS.'footer.snippet.php'); ?>
diff --git a/templates/single.inc.php b/templates/single.inc.php
index b858181..64e7f6d 100644
--- a/templates/single.inc.php
+++ b/templates/single.inc.php
@@ -39,6 +39,32 @@
// edit post
if(!empty($_POST['action']) && $_POST['action'] == 'edit') {
+ // check changes to attachments
+ $attached_files = db_get_attached_files($_POST['id']);
+ if(!empty($attached_files)) {
+ $files_ids = array_column($attached_files, 'id');
+
+ if(empty($_POST['attachments'])) {
+ // remove ALL attachments
+ $to_remove = $files_ids;
+ } else {
+ // remove specified attachments
+ /*
+ $to_remove = array_filter($attached_files, function($v) {
+ return !in_array($v['id'], $_POST['attachments']);
+ });
+ */
+ $to_remove = array_diff($files_ids, $_POST['attachments']);
+ }
+
+ if(count($to_remove) > 0) {
+ if(!detatch_files($to_remove, $_POST['id'])) {
+ // could not remove attachments
+ // var_dump($to_remove);
+ }
+ }
+ }
+
$result = db_update((int) $_POST['id'], $_POST['content']);
if(!$result) {
@@ -71,12 +97,41 @@
<li class="single-post" data-post-id="<?= $post['id'] ?>">
<?php if($action == 'edit'): ?>
<form action="" method="post" class="edit">
- <textarea name="content" maxlength="<?= $config['max_characters'] ?>"><?= $post['post_content'] ?></textarea>
- <p id="count"><?= $config['max_characters'] ?></p>
-
<input type="hidden" name="action" value="edit" />
<input type="hidden" name="id" value="<?= $post['id'] ?>" />
- <input type="submit" class="button" value="Update this post" />
+
+ <textarea name="content" maxlength="<?= $config['max_characters'] ?>"><?= $post['post_content'] ?></textarea>
+
+ <div class="post-nav">
+ <!--<label id="post-attachments-label">Add Files<input type="file" multiple="multiple" name="attachments[]" id="post-attachments" accept="image/*" /></label>
+ <div id="post-droparea" class="hidden">Add Files</div>-->
+ <ul id="post-attachments-list">
+ <?php
+ $attachments = db_get_attached_files($post['id']);
+ ?>
+ <?php if(!empty($attachments)): ?>
+ <?php foreach($attachments as $a): ?>
+ <?php if(strpos($a['file_mime_type'], 'image') === 0): ?>
+ <?php
+ $abs = ROOT.DS.get_file_path($a);
+ list($width, $height, $_, $size_string) = getimagesize($abs);
+ $url = $config['url'] .'/'. get_file_path($a);
+ ?>
+ <li>
+ <label>
+ <input type="checkbox" name="attachments[]" value="<?= $a['id'] ?>" checked />
+ <img class="file-preview" src="<?= $url ?>" alt="<?= $a['file_original'] ?>" <?= $size_string ?> loading="lazy" />
+ <?= $a['file_original'] ?>
+ </label>
+ </li>
+ <?php else: ?>
+ <?php endif; ?>
+ <?php endforeach; ?>
+ <?php endif; ?>
+ </ul>
+ <p id="count"><?= $config['max_characters'] ?></p>
+ <input type="submit" class="button" value="Update this post" />
+ </div>
</form>
<?php else: ?>
<?php
@@ -85,6 +140,9 @@
$datetime = date_format($date, 'Y-m-d H:i:s');
$formatted_time = date_format($date, 'M d Y H:i');
+
+ $attachments = db_get_attached_files($post['id']);
+ // var_dump($attachments);
?>
<span class="post-timestamp">
<time class="published" datetime="<?= $datetime ?>" data-unix-time="<?= $post['post_timestamp'] ?>"><?= $formatted_time ?></time>
@@ -103,6 +161,29 @@
</ul><?php endif; ?>
</nav>
<div class="post-content"><?= nl2br(autolink($post['post_content'])) ?></div>
+ <?php if(!empty($attachments)): ?>
+ <ul class="post-attachments">
+ <?php foreach($attachments as $a): ?>
+ <li>
+ <?php if(strpos($a['file_mime_type'], 'image') === 0): ?>
+ <?php
+ $abs = ROOT.DS.get_file_path($a);
+ list($width, $height, $_, $size_string) = getimagesize($abs);
+ $url = $config['url'] .'/'. get_file_path($a);
+ ?>
+ <a href="<?= $url ?>">
+ <picture>
+ <source srcset="<?= $url ?>" type="image/jpeg" />
+ <img src="<?= $url ?>" alt="<?= $a['file_original'] ?>" <?= $size_string ?> loading="lazy" />
+ </picture>
+ </a>
+ <?php else: ?>
+ <a href="<?= $url ?>" download="<?= $a['file_original'] ?>"><?= $a['file_original'] ?></a>
+ <?php endif; ?>
+ </li>
+ <?php endforeach; ?>
+ </ul>
+ <?php endif; ?>
<?php if($action == 'delete'): ?>
<form action="" method="post" class="delete">
<input type="hidden" name="action" value="delete" />
diff --git a/templates/timeline.inc.php b/templates/timeline.inc.php
index bc06de9..6dd37c1 100644
--- a/templates/timeline.inc.php
+++ b/templates/timeline.inc.php
@@ -36,6 +36,8 @@
$datetime = date_format($date, 'Y-m-d H:i:s');
$formatted_time = date_format($date, 'M d Y H:i');
+
+ $attachments = db_get_attached_files($post['id']);
?>
<a class="post-timestamp" href="<?= $config['url'] ?>/<?= $post['id'] ?>">
<time class="published" datetime="<?= $datetime ?>" data-unix-time="<?= $post['post_timestamp'] ?>"><?= $formatted_time ?></time>
@@ -50,6 +52,34 @@
</ul><?php endif; ?>
</nav>
<div class="post-content"><?= nl2br(autolink($post['post_content'])) ?></div>
+ <?php if(!empty($attachments)): ?>
+ <?php
+ $attachments_total = count($attachments);
+ // only display the first attachment on the timeline
+ array_splice($attachments, 1);
+ ?>
+ <ul class="post-attachments">
+ <?php foreach($attachments as $a): ?>
+ <li title="<?= ($attachments_total > 1) ? 'and '.($attachments_total-1).' more' : '' ?>">
+ <?php if(strpos($a['file_mime_type'], 'image') === 0): ?>
+ <?php
+ $abs = ROOT.DS.get_file_path($a);
+ list($width, $height, $_, $size_string) = getimagesize($abs);
+ $url = $config['url'] .'/'. get_file_path($a);
+ ?>
+ <a href="<?= $config['url'] ?>/<?= $post['id'] ?>">
+ <picture>
+ <source srcset="<?= $url ?>" type="image/jpeg" />
+ <img src="<?= $url ?>" alt="<?= $a['file_original'] ?>" <?= $size_string ?> loading="lazy" />
+ </picture>
+ </a>
+ <?php else: ?>
+ <a href="<?= $url ?>" download="<?= $a['file_original'] ?>"><?= $a['file_original'] ?></a>
+ <?php endif; ?>
+ </li>
+ <?php endforeach; ?>
+ </ul>
+ <?php endif; ?>
</li>
<?php endforeach; ?>
</ul>