Compare commits

...
Sign in to create a new pull request.

14 commits

Author SHA1 Message Date
f552fff6b8 Merge pull request 'Add QuickPhoto Addon: Elegant BBCode simplification for images' (#1639) from loma-one/friendica-addons:loma-one-patch-1 into develop
Reviewed-on: friendica/friendica-addons#1639
Reviewed-by: Hypolite Petovan <hypolite@mrpetovan.com>
2026-03-07 17:20:56 +01:00
2e9f640724 quickphoto/quickphoto.php aktualisiert
DI::l10n()->t inserted
2026-03-07 17:09:57 +01:00
59ece8856c Translation file 2026-03-07 15:13:03 +01:00
aa47cc6e04 quickphoto/quickphoto.js aktualisiert
Make sure the translation is applied.
2026-03-07 15:13:03 +01:00
4a133456ce quickphoto/quickphoto.php aktualisiert
Provides a translation
2026-03-07 15:13:03 +01:00
22026ed533 quickphoto/quickphoto.js aktualisiert
"Description" instead of "Bildbeschreibung"
2026-03-07 15:13:03 +01:00
29e8c2875c quickphoto/README.md aktualisiert
Inserted | in `[img]|filename description[/img]`
2026-03-07 15:13:03 +01:00
0ae86dafa0 quickphoto/README.md hinzugefügt 2026-03-07 15:13:03 +01:00
ec3b0c5941 Add QuickPhoto Addon: Elegant BBCode simplification for images
This PR introduces the QuickPhoto addon, designed to improve the user experience within the Friendica editor. It addresses the issue of long, cluttered BBCodes (monster-links) that appear when images are inserted via drag-and-drop or the image uploader.
Key Features

- Automatic Simplification: Replaces complex [url=...][img=...]...[/img][/url] structures with a human-readable shorthand: [img]filename|description[/img].
- Seamless Reconstruction: Automatically restores the full, valid Friendica BBCode before previewing or submitting a post, ensuring 100% compatibility with the server.
- Enhanced Readability: Keeps the editor clean and focus-oriented while writing long posts with multiple images.

Technical Highlights & Optimizations

- Resource Efficient: The JavaScript is strictly scoped. It only becomes active if a textarea is present and visible in the DOM.
- Zero Latency Typing: Implements requestIdleCallback and throttling (500ms) to ensure that the simplification process never interferes with the user's typing flow or causes UI lag.
- Background Throttling: Logic is suspended when the browser tab is inactive (document.hidden) to save CPU and battery life.
- LocalStorage Cache: Uses a local cache for image data (auto-cleaned after 12 hours) to ensure reliability during a single editing session.

Testing performed

- Tested with the standard "Jot" editor and the newer Compose/Comment templates.
- Verified that image previews render correctly (reconstruction triggers on preview click).
- Verified that the final post contains the correct full BBCode on the server side.
- Confirmed that Drag & Drop inserts are handled immediately.
2026-03-07 15:13:03 +01:00
3509144228 Add QuickPhoto Addon: Elegant BBCode simplification for images
This PR introduces the QuickPhoto addon, designed to improve the user experience within the Friendica editor. It addresses the issue of long, cluttered BBCodes (monster-links) that appear when images are inserted via drag-and-drop or the image uploader.
Key Features

- Automatic Simplification: Replaces complex [url=...][img=...]...[/img][/url] structures with a human-readable shorthand: [img]filename|description[/img].
- Seamless Reconstruction: Automatically restores the full, valid Friendica BBCode before previewing or submitting a post, ensuring 100% compatibility with the server.
- Enhanced Readability: Keeps the editor clean and focus-oriented while writing long posts with multiple images.

Technical Highlights & Optimizations

- Resource Efficient: The JavaScript is strictly scoped. It only becomes active if a textarea is present and visible in the DOM.
- Zero Latency Typing: Implements requestIdleCallback and throttling (500ms) to ensure that the simplification process never interferes with the user's typing flow or causes UI lag.
- Background Throttling: Logic is suspended when the browser tab is inactive (document.hidden) to save CPU and battery life.
- LocalStorage Cache: Uses a local cache for image data (auto-cleaned after 12 hours) to ensure reliability during a single editing session.

Testing performed

- Tested with the standard "Jot" editor and the newer Compose/Comment templates.
- Verified that image previews render correctly (reconstruction triggers on preview click).
- Verified that the final post contains the correct full BBCode on the server side.
- Confirmed that Drag & Drop inserts are handled immediately.
2026-03-07 15:13:03 +01:00
ade3c6b22e Merge pull request 'IRC: Use friendica nickname as suggested IRC nickname, when signed in' (#1637) from marcusxs/friendica-addons:default_irc_nickname_to_friendica_nickname into develop
Reviewed-on: friendica/friendica-addons#1637
2026-03-07 15:07:09 +01:00
Marcus F.
31c130436c IRC: Use friendica nickname as suggested IRC nickname, when signed in 2026-03-07 13:31:50 +01:00
8a4715271c Merge pull request 'Mailstream: respect blocked/ignored/collapsed contact settings' (#1640) from mexon/friendica-addons:mat/mailstream-block into develop
Reviewed-on: friendica/friendica-addons#1640
Reviewed-by: Hypolite Petovan <hypolite@mrpetovan.com>
2026-03-07 04:12:30 +01:00
Matthew Exon
ed70b2ca27 Mailstream: respect blocked/ignored/collapsed contact settings 2026-03-02 18:13:28 +01:00
6 changed files with 264 additions and 5 deletions

View file

@ -74,6 +74,7 @@ function irc_content()
{
$baseurl = DI::baseUrl() . '/addon/irc';
$o = '';
$usernick = '';
/* set the list of popular channels */
if (DI::userSession()->getLocalUserId()) {
@ -81,6 +82,7 @@ function irc_content()
if (!$sitechats) {
$sitechats = DI::config()->get('irc', 'sitechats');
}
$usernick = "nick=" . DI::userSession()->getLocalUserNickname() . "&";
} else {
$sitechats = DI::config()->get('irc','sitechats');
}
@ -117,7 +119,7 @@ function irc_content()
$o .= <<< EOT
<h2>IRC chat</h2>
<p><a href="https://tldp.org/HOWTO/IRC/beginners.html" target="_blank" rel="noopener noreferrer">A beginner's guide to using IRC. [en]</a></p>
<iframe src="//web.libera.chat?channels=$channels" style="width:100%; max-width:900px; height: 600px;"></iframe>
<iframe src="//web.libera.chat?{$usernick}channels=$channels" style="width:100%; max-width:900px; height: 600px;"></iframe>
EOT;
return $o;

View file

@ -166,7 +166,35 @@ function mailstream_send_hook(array $data)
return;
}
if (!mailstream_send($data['message_id'], $item, $user)) {
$author = DBA::selectFirst('contact', ['nick', 'blocked', 'uri-id'], ['id' => $data['author-id'], 'self' => false]);
if (!DBA::isResult($author)) {
DI::logger()->error('could not find author', ['guid' => $item['guid'], 'author-id' => $data['author-id']]);
return;
}
if ($author['blocked']) {
DI::logger()->info('author is blocked', ['guid' => $item['guid'], 'author-id' => $data['author-id']]);
return;
}
$collapsed = false;
$user_contact = DBA::selectFirst('user-contact', ['cid', 'blocked', 'ignored', 'collapsed'], ['uid' => $item['uid'], 'uri-id' => $item['author-uri-id']]);
if (!DBA::isResult($user_contact)) {
$user_contact = DBA::selectFirst('user-contact', ['cid', 'blocked', 'ignored', 'collapsed'], ['uid' => $item['uid'], 'cid' => $item['author-id']]);
}
if (DBA::isResult($user_contact)) {
if ($user_contact['blocked']) {
DI::logger()->info('author is blocked', ['guid' => $item['guid'], 'cid' => $user_contact['cid']]);
return;
}
if ($user_contact['ignored']) {
DI::logger()->info('author is ignored', ['guid' => $item['guid'], 'cid' => $user_contact['cid']]);
return;
}
if ($user_contact['collapsed']) {
$collapsed = true;
}
}
if (!mailstream_send($data['message_id'], $item, $user, $collapsed)) {
DI::logger()->debug('send failed, will retry', $data);
if (!Worker::defer()) {
DI::logger()->error('failed and could not defer', $data);
@ -220,6 +248,7 @@ function mailstream_post_hook(array &$item)
$send_hook_data = [
'uid' => $item['uid'],
'contact-id' => $item['contact-id'],
'author-id' => $item['author-id'],
'uri' => $item['uri'],
'message_id' => $message_id,
'tries' => 0,
@ -406,10 +435,11 @@ function mailstream_subject(array $item): string
* @param string $message_id ID of the message (RFC 1036)
* @param array $item content of the item
* @param array $user results from the user table
* @param bool $collapsed true if the content should be hidden
*
* @return bool True if this message has been completed. False if it should be retried.
*/
function mailstream_send(string $message_id, array $item, array $user): bool
function mailstream_send(string $message_id, array $item, array $user, bool $collapsed): bool
{
if (!is_array($item)) {
DI::logger()->error('item is empty', ['message_id' => $message_id]);
@ -427,10 +457,16 @@ function mailstream_send(string $message_id, array $item, array $user): bool
require_once(dirname(__file__) . '/phpmailer/class.phpmailer.php');
$item['body'] = Post\Media::addAttachmentsToBody($item['uri-id'], $item['body']);
if ($collapsed) {
$item['body'] = DI::l10n()->t('Content from %s is collapsed', $item['author-name']);
} else {
$item['body'] = Post\Media::addAttachmentsToBody($item['uri-id'], $item['body']);
}
$attachments = [];
mailstream_do_images($item, $attachments);
if (!$collapsed) {
mailstream_do_images($item, $attachments);
}
$frommail = DI::config()->get('mailstream', 'frommail');
if ($frommail == '') {
$frommail = 'friendica@localhost.local';

45
quickphoto/README.md Normal file
View file

@ -0,0 +1,45 @@
# QuickPhoto Addon for Friendica
QuickPhoto is a Friendica addon that simplifies working with images in the editor. It automatically replaces long, cumbersome BBCode structures with a compact shorthand notation, without affecting functionality or compatibility.
---
## Features
- **Automatic Simplification:** Converts "monster BBCodes" like `[url=...][img=...]...[/img][/url]` instantly into the handy format `[img]|filename description[/img]`.
- **Intelligent Reconstruction:** Before submitting or previewing, the shorthand code is quickly converted back into the original, valid Friendica BBCode.
- **Real-Time Processing:** Responds immediately to drag & drop, copy & paste, and inserting images via editor buttons.
- **Focus Safety:** Cursor management ensures the focus remains stable during automatic conversion while typing.
- **Maximum Compatibility:** Supports both the standard Jot editor and the Compose module, as well as reply fields.
- **Local Cache:** Image data is securely stored in the browser's localStorage and automatically cleared after 12 hours.
---
## How It Works
The addon operates in a hybrid manner:
- **Frontend:** A JavaScript watcher scans textareas and simplifies complex image links for better readability while writing.
- **Interface:** It integrates deeply with Friendica's jQuery functions to ensure that preview and save functions always receive the correct original data.
- **Events:** By intercepting submit and preview clicks, it guarantees that shorthand codes are never sent to the server in a format it cannot interpret.
---
## Installation
1. Create a folder named `quickphoto` in the `addon/` directory of your Friendica installation.
2. Place the file `quickphoto.php` in this folder.
3. Place the file `quickphoto.js` in the same folder.
4. Enable the addon in the Friendica administration area under **Addons**.
---
MIT License
Copyright (c) 2024-2026 Friendica Project & Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,22 @@
# ADDON quickphoto
# Copyright (C)
# This file is distributed under the same license as the Friendica quickphoto addon package.
#
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-07 10:18+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: quickphoto.php:18
msgid "Image description"
msgstr ""

126
quickphoto/quickphoto.js Normal file
View file

@ -0,0 +1,126 @@
(function() {
const monsterPattern = /\[url=(.*?)\]\[img=(.*?)\](.*?)\[\/img\]\[\/url\]/gi;
let throttleTimer;
const i18nDesc = (window.qp_i18n && window.qp_i18n.imageDesc) ? window.qp_i18n.imageDesc : "Image description";
const cleanupOldEntries = () => {
const now = Date.now();
const twelveHours = 12 * 60 * 60 * 1000;
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith('qp_')) {
try {
const data = JSON.parse(localStorage.getItem(key));
if (data && data.timestamp && (now - data.timestamp > twelveHours)) {
localStorage.removeItem(key);
}
} catch (e) { localStorage.removeItem(key); }
}
}
};
const simplify = (text) => {
if (!text || !text.includes('[url=')) return text;
return text.replace(monsterPattern, (match, urlPart, imgPart, existingDesc) => {
const fileName = imgPart.split('/').pop();
const storageKey = `qp_${fileName}`;
localStorage.setItem(storageKey, JSON.stringify({
url: urlPart,
img: imgPart,
timestamp: Date.now()
}));
let userDesc = existingDesc.trim() || i18nDesc;
return `[img]${fileName}|${userDesc}[/img]`;
});
};
const reconstruct = (text) => {
if (!text || !text.includes('[img]')) return text;
return text.replace(/\[img\](.*?)\|(.*?)\[\/img\]/g, (match, fileName, desc) => {
const data = localStorage.getItem(`qp_${fileName}`);
if (data) {
const parsed = JSON.parse(data);
const finalDesc = (desc === i18nDesc) ? "" : desc;
return `[url=${parsed.url}][img=${parsed.img}]${finalDesc}[/img][/url]`;
}
return match;
});
};
const applySimplify = (textarea) => {
if (!textarea || !textarea.value || !textarea.value.includes('[/img]')) return;
(window.requestIdleCallback || function(cb) { return setTimeout(cb, 1); })(() => {
const current = textarea.value;
const simple = simplify(current);
if (current !== simple) {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
textarea.value = simple;
textarea.setSelectionRange(start, end);
}
});
};
if (typeof jQuery !== 'undefined') {
const originalVal = jQuery.fn.val;
jQuery.fn.val = function(value) {
if (arguments.length === 0 && this.is('textarea')) {
return reconstruct(originalVal.call(this));
}
if (arguments.length > 0 && this.is('textarea')) {
return originalVal.call(this, simplify(value));
}
return originalVal.apply(this, arguments);
};
}
document.addEventListener('drop', (e) => {
if (e.target.tagName === 'TEXTAREA') {
setTimeout(() => applySimplify(e.target), 150);
}
}, true);
document.addEventListener('input', (e) => {
if (e.target.tagName === 'TEXTAREA') {
clearTimeout(throttleTimer);
throttleTimer = setTimeout(() => applySimplify(e.target), 500);
}
});
document.addEventListener('click', (e) => {
const btn = e.target.closest(
'#wall-submit-preview, #profile-jot-submit, #wall-submit-submit, #jot-submit, ' +
'[id^="comment-edit-submit-"], [id^="comment-edit-preview-link-"]'
);
if (btn) {
const textareas = document.querySelectorAll('textarea');
if (textareas.length > 0) {
textareas.forEach(textarea => {
textarea.value = reconstruct(textarea.value);
if (btn.id.includes('preview')) {
setTimeout(() => applySimplify(textarea), 1000);
}
});
}
}
}, true);
setInterval(() => {
if (document.hidden) return;
const textareas = document.querySelectorAll('textarea');
if (textareas.length === 0) return;
textareas.forEach(textarea => {
if (textarea.offsetParent !== null) {
applySimplify(textarea);
}
});
}, 2500);
cleanupOldEntries();
})();

28
quickphoto/quickphoto.php Normal file
View file

@ -0,0 +1,28 @@
<?php
/**
* Name: QuickPhoto
* Description: Replaces the BBCode for inserted images and provides a placeholder for image descriptions.
* Version: 1.2
* Author: Matthias Ebers <https://loma.ml/profile/feb>
*/
use Friendica\Core\Hook;
use Friendica\DI;
function quickphoto_install() {
Hook::register('page_header', 'addon/quickphoto/quickphoto.php', 'quickphoto_header');
Hook::register('post_post', 'addon/quickphoto/quickphoto.php', 'quickphoto_post_hook');
}
function quickphoto_header(&$header) {
$desc_label = DI::l10n()->t('Image description');
$js_label = addslashes($desc_label);
$header .= "\n" . '<script type="text/javascript">var qp_i18n = { imageDesc: "' . $js_label . '" };</script>';
$header .= "\n" . '<script type="text/javascript" src="/addon/quickphoto/quickphoto.js?v=5.1"></script>' . "\n";
}
function quickphoto_post_hook(&$item) {
// Placeholder
}