Compare commits

..

45 Commits
10.4 ... 10.4.2

Author SHA1 Message Date
Mario
44a0311798 Merge branch 'master' of https://framagit.org/hubzilla/core 2025-08-08 07:51:42 +00:00
Mario
2ac06fef9a version 10.4.2 2025-08-08 07:51:12 +00:00
Mario
5b6a38aa2d changelog 2025-08-08 07:48:14 +00:00
Mario
37b9919905 bump version 2025-08-07 22:53:26 +00:00
Mario
283c018d07 implement item_custom_display hook in mod HQ 2025-08-07 22:51:40 +00:00
Mario
51c79aecac make item_normal() deal with various item types and add missing reactions modal 2025-08-07 22:36:35 +00:00
Mario
9343f00918 improve memory consumption when deleting big threads 2025-08-07 19:05:58 +00:00
Mario
962b4e5b4c fix php error
(cherry picked from commit eeef6cfd00)

Co-authored-by: Mario <mario@mariovavti.com>
2025-08-07 14:57:11 +00:00
Mario
eeef6cfd00 fix php error 2025-08-07 14:55:00 +00:00
Mario
96c7912fa6 update the contact edit header so that both, image and text are linked to the profile 2025-08-02 11:48:50 +00:00
Mario
91f1f8ed7c version 10.4.1 2025-07-31 08:46:43 +00:00
Mario
3f7ed72ece changelogà 2025-07-31 08:43:31 +00:00
Mario
3385c694aa add delay before collapsing 2025-07-31 08:22:01 +00:00
Mario
a922f5ca6c bump version 2025-07-30 19:20:35 +00:00
Mario
88977a406c imagesLoaded() should not be required in updateConvItems() anymore since we call it before updateConvItems() already (famous last words). 2025-07-30 19:19:00 +00:00
Mario
e98f019b16 fix regression in pubstream tag view, use correct prefixes in pub_tagadelic() and some whitespace fixes 2025-07-30 16:22:08 +00:00
Mario
50f5fcb16b always update photo DB entries on move - fixes issue where photos were not found by photo picker after folder rename as reported by chris 2025-07-30 06:45:25 +00:00
Mario
bf35a661bf remove trailing comma 2025-07-29 11:02:43 +00:00
Mario
ecc94cdecd fix a weird rendering issue which was triggered by load events fireing after the timeout was already in action 2025-07-29 09:28:00 +00:00
Mario
e79a70d2b1 var > const and style 2025-07-27 10:24:53 +00:00
Mario
6916a825ee move the freshly created content to the center instead of top and use smooth scrolling 2025-07-27 10:23:23 +00:00
Mario
b5a798f068 fix comment preview and scroll to the just created comment after posting it 2025-07-27 09:52:48 +00:00
Mario
e5c54fadea Merge branch 'update-derived-theme-tutorial' into 'dev'
Update derived theme tutorial

See merge request hubzilla/core!2219
2025-07-27 08:15:15 +00:00
Mario
f2217e9016 Merge branch 'fix-pubstream-tagcloud-tpl' into 'dev'
Fix pubstream tagcloud and category cloud

See merge request hubzilla/core!2218
2025-07-27 08:14:41 +00:00
Saiwal K
4d6bebb846 Fix pubstream tagcloud and category cloud 2025-07-27 08:14:41 +00:00
Harald Eilertsen
2e9b1d25e1 Replace mention of theme_init function
The theme_init function is no longer needed, and the example isn't using
it. So we replace it with a bit more description of the theme info in
the comment, and how that works.
2025-07-24 12:50:50 +02:00
Harald Eilertsen
8604ba016e Update derived theme tutorial
Adds another lesson on customising the theme using styles and overriding
templates.

Also some minor language cleanup and add some introductory text before
lesson 1.
2025-07-24 12:40:13 +02:00
Mario
cc35afa4c9 bump version 2025-07-23 12:07:45 +00:00
Mario
f01c5ad0c1 remove logging 2025-07-23 12:07:21 +00:00
Mario
7b6a8fdba1 fix issue where reply modal would not show anymore after reactions loaded 2025-07-23 12:06:48 +00:00
Mario
2c9f184a09 tagcloud fixes 2025-07-23 08:19:40 +00:00
Mario
6f158f37c9 Merge branch 'improve-derived-themes' into 'dev'
Add 'extends' attribute to theme info

See merge request hubzilla/core!2217
2025-07-23 07:54:30 +00:00
Mario
f88d2ba0c4 Merge branch 'tag-cloud-tpl-fix' into 'dev'
Add tpl for tagcloud and directory keyword cloud

See merge request hubzilla/core!2216
2025-07-23 07:53:10 +00:00
Saiwal K
170f157aae Add tpl for tagcloud and directory keyword cloud 2025-07-23 07:53:09 +00:00
Mario
82817b4532 tagadelic: look up toplevels only
(cherry picked from commit 3eeaca4aab)

Co-authored-by: Mario <mario@mariovavti.com>
2025-07-22 10:34:19 +00:00
Mario
3eeaca4aab tagadelic: look up toplevels only 2025-07-22 10:32:57 +00:00
Mario
a3dd4a4d3d revert cleanup template
(cherry picked from commit c383e6af80)

Co-authored-by: Mario <mario@mariovavti.com>
2025-07-22 10:22:38 +00:00
Mario
c383e6af80 revert cleanup template 2025-07-22 10:21:34 +00:00
Mario
517e14cc38 cleanup template
(cherry picked from commit eb3dfb2488)

Co-authored-by: Mario <mario@mariovavti.com>
2025-07-22 10:17:05 +00:00
Mario
eb3dfb2488 cleanup template 2025-07-22 10:16:09 +00:00
Mario
70e8648d91 the item_wall directive is not required here since we are looking for parent items which we own - remove it since it will make the DB engine use the wrong index in certain cases. also remove duplication of item_type directive when looking up articles
(cherry picked from commit ae8e9bc353)

Co-authored-by: Mario <mario@mariovavti.com>
2025-07-22 09:33:34 +00:00
Mario
ae8e9bc353 the item_wall directive is not required here since we are looking for parent items which we own - remove it since it will make the DB engine use the wrong index in certain cases. also remove duplication of item_type directive when looking up articles 2025-07-22 09:32:20 +00:00
Harald Eilertsen
88227b9155 Rename derived theme tutorial file 2025-07-20 20:33:56 +02:00
Harald Eilertsen
ae77ab7f20 Add derived theme tutorial to help index 2025-07-20 20:28:39 +02:00
Harald Eilertsen
308380fe52 Add 'extends' attribute to theme info
To create a derived (or child) theme, the array held in
`App::$theme_info` must contain a key `extends` that contains the name
of the parent theme as a value.

The derived theme tutorial would suggest adding the key in the
`themename_init()` function, however this does not work. The theme info
is repopulated from the theme info file after the theme init function
has run, and so the `extends` key disappears.

This patch adds `extends` as a first class attribute for themes, so that
it can be specified directly in the theme info in the top file comment
block. It also updates the derived theme tutorial to reflect this
change.
2025-07-20 20:23:25 +02:00
23 changed files with 362 additions and 221 deletions

View File

@@ -1,3 +1,31 @@
Hubzilla 10.4.2 (2025-08-08)
- Implement item_custom_display hook in mod HQ
- Refactor item fetching functions to reflect item_normal() changes
- Refactor item_normal() to optionally deal with various item types
- Fix missing reactions modal in threaded_conversation.tpl
- Improve memory consumption in drop_related() to fix deleting of big threads
- Fix PHP error with the potential to stuff up the queueworker
- Update the contact edit header so that both, image and text are linked to the profile
- Articles: refactor to reflect item_normal() changes
- Cards: refactor to reflect item_normal() changes
Hubzilla 10.4.1 (2025-07-31)
- Fix regression in pubstream tag view
- Fix photos meta data not updated when renaming folder in files app
- Fix syntax error in custom emoji sample code
- Fix rendering issue when image load event triggered after timeout
- Fix comment preview not always displayed
- Fix new created comment rendering offscreen
- Update derived theme tutorial and add it to the help index
- Add 'extends' attribute to theme info
- Fix reply modal remaining hidden after reactions loaded
- Refactor tagcloud to use smarty template file
- Fix regression in tagadelic
- Fix possible performance issue with archive widget
- Fix various addons which still used the deprecated $a global
Hubzilla 10.4 (2025-07-15)
- Add support for did:key verification method to checkEddsaSignature()
- Introduce util/init_sys_channel to create the sys channel if required

View File

@@ -2578,7 +2578,7 @@ class Activity {
$ptr = [$act->obj['url']];
}
foreach ($ptr as $vurl) {
if (array_key_exists('mediaType', $vurl) && $vurl['mediaType'] === 'text/html') {
if (isset($vurl['mediaType']) && $vurl['mediaType'] === 'text/html') {
$s['plink'] = $vurl['href'];
break;
}
@@ -2590,7 +2590,7 @@ class Activity {
}
}
if (!(isset($s['plink']) && $s['plink'])) {
if (empty($s['plink'])) {
$s['plink'] = $s['mid'];
}

View File

@@ -60,6 +60,9 @@ class Hq extends \Zotlabs\Web\Controller {
if($r) {
$target_item = $r[0];
call_hooks('item_custom_display', $target_item);
if (intval($target_item['uid']) === intval($sys['channel_id'])) {
$sys_item = true;
}

View File

@@ -53,12 +53,12 @@ class Item extends Controller {
if (argc() > 1 && argv(1) !== 'drop') {
$x = q("select uid, item_wall, llink, uuid from item where uuid = '%s' order by item_wall desc",
$x = q("select uid, item_wall, item_type, llink, uuid from item where uuid = '%s' order by item_wall desc",
dbesc(argv(1))
);
if ($x) {
if ($x[0]['item_wall']) {
if ($x[0]['item_wall'] && $x[0]['item_type'] === ITEM_TYPE_POST) {
$c = channelx_by_n($x[0]['uid']);
if ($c) {
goaway(z_root() . '/channel/' . $c['channel_address'] . '?mid=' . $x[0]['uuid']);

View File

@@ -67,7 +67,7 @@ class Like extends Controller {
$items = conv_sort($items, 'commented');
}
else {
$item = item_by_item_id($arr['item']['id'], $arr['item']['parent']);
$item = item_by_item_id($arr['item']['id'], $arr['item']['parent'], type: $arr['item']['item_type']);
xchan_query($item, true);
$item = fetch_post_tags($item, true);
}
@@ -127,7 +127,7 @@ class Like extends Controller {
$extended_like = false;
$object = $target = null;
$post_type = EMPTY_STR;
$obj_type = EMPTY_STR;
$obj_type = EMPTY_STR;
if (argc() == 3) {
@@ -305,8 +305,6 @@ class Like extends Controller {
// parent, copy that as well.
if ($r) {
$obj_type = $r[0]['obj_type'];
if ($r[0]['uid'] === $sys_channel['channel_id'] && local_channel()) {
$r = [copy_of_pubitem(App::get_channel(), $r[0]['mid'])];
}
@@ -322,6 +320,8 @@ class Like extends Controller {
$item = $r[0];
$owner_uid = $r[0]['uid'];
$owner_aid = $r[0]['aid'];
$obj_type = $r[0]['obj_type'];
$item_type = $r[0]['item_type'];
if ((array_key_exists('owner', $item)) && intval($item['owner']['abook_self']))
$can_comment = perm_is_allowed($item['uid'], $observer['xchan_hash'], 'post_comments');
@@ -362,7 +362,7 @@ class Like extends Controller {
$multi_undo = true;
}
$item_normal = item_normal();
$item_normal = item_normal(type: $item_type);
$r = q("SELECT id, parent, uid, verb FROM item WHERE verb in ( $verbs ) $item_normal
AND author_xchan = '%s' AND thr_parent = '%s' and uid = %d ",

View File

@@ -137,7 +137,7 @@ class Pubstream extends \Zotlabs\Web\Controller {
'$page' => ((\App::$pager['page'] != 1) ? \App::$pager['page'] : 1),
'$search' => '',
'$xchan' => '',
'$order' => 'comment',
'$order' => 'post',
'$file' => '',
'$cats' => '',
'$tags' => (($hashtags) ? urlencode($hashtags) : ''),
@@ -173,17 +173,15 @@ class Pubstream extends \Zotlabs\Web\Controller {
$site_firehose_sql = " and owner_xchan in (select channel_hash from channel where channel_system = 0 and channel_removed = 0) ";
}
if(Config::Get('system','public_list_mode'))
$page_mode = 'list';
else
$page_mode = 'client';
$page_mode = 'client';
$blog_mode = Config::Get('system',' public_list_mode');
if ($blog_mode) {
$page_mode = 'list';
}
if(x($hashtags)) {
$sql_extra .= protect_sprintf(term_query('item', $hashtags, TERM_HASHTAG, TERM_COMMUNITYTAG));
$sql_extra_order = " ORDER BY item.created DESC ";
$thread_top = '';
}
$net_query2 = (($net) ? " and xchan_network = '" . protect_sprintf(dbesc($net)) . "' " : '');
@@ -247,30 +245,20 @@ class Pubstream extends \Zotlabs\Web\Controller {
}
}
// Then fetch all the children of the parents that are on this page
$parents_str = '';
if($r) {
$items = items_by_parent_ids($r);
$items = items_by_parent_ids($r, blog_mode: $blog_mode);
// use effective_uid param of xchan_query to help sort out comment permission
// for sys_channel owned items.
xchan_query($items, true, local_channel());
$items = fetch_post_tags($items,true);
if (!$hashtags) {
$items = conv_sort($items, $ordering);
}
$items = conv_sort($items, $ordering);
}
}
$mode = (($hashtags) ? 'pubstream-new' : 'pubstream');
$o .= conversation($items,$mode,$update,$page_mode);
$o .= conversation($items, 'pubstream', $update, $page_mode);
if($mid)
$o .= '<div id="content-complete"></div>';

View File

@@ -70,7 +70,7 @@ require_once('include/security.php');
define('PLATFORM_NAME', 'hubzilla');
define('STD_VERSION', '10.4');
define('STD_VERSION', '10.4.2');
define('ZOT_REVISION', '6.0');
define('DB_UPDATE_VERSION', 1263);

View File

@@ -79,6 +79,7 @@
<div id="developers" class="doco-section">
<div class="vstack">
<a class="" href="/help/developer/developers_guide">Guide</a>
<a href="/help/tutorials/derived_theme">Tutorial: Creating a derived theme</a>
</div>
</div>
</div>

View File

@@ -1,96 +0,0 @@
### Creating a Derived Theme
**Lesson 1**
A derived theme takes most of the settings from its "parent" theme and lets you change a few things to your liking without creating an entire theme package.
To create a derived theme, first choose a name. For our example we'll call our theme 'mytheme'. Hopefully you'll be a bit more creative. But throughout this document, wherever you see 'mytheme', replace that with the name you chose.
**Directory Structure**
First you need to create a theme directory structure. We'll keep it simple. We need a php directory and a css directory. Here are the Unix/Linux commands to do this. Assume that 'mywebsite' is your top level hubzilla folder.
cd mywebsite
mkdir view/theme/mytheme
mkdir view/theme/mytheme/css
mkdir view/theme/mytheme/php
Great. Now we need a couple of files. The first one is your theme info file, which describes the theme.
It will be called view/theme/mytheme/php/theme.php (clever name huh?)
Inside it, put the following information - edit as needed
<?php
/**
* * Name: Mytheme
* * Description: Sample Derived theme
* * Version: 1.0
* * Author: Your Name
* * Compat: Red [*]
*
*/
function mytheme_init(&$a) {
App::$theme_info['extends'] = 'redbasic';
}
Remember to rename the mytheme_init function with your theme name. In this case we will be extending the theme 'redbasic'.
Now create another file. We call this a PCSS file, but it's really a PHP file.
The file is called view/theme/mytheme/php/style.php
In it, put the following:
<?php
require_once('view/theme/redbasic/php/style.php');
echo @file_get_contents('view/theme/mytheme/css/style.css');
That's it. This tells the software to read the PCSS information for the redbasic theme first, and then read our CSS file which will just consist of changes we want to make from our parent theme (redbasic).
Now create the actual CSS file for your theme. Put it in view/theme/mytheme/css/style.css (where we just told the software to look for it). For our example, we'll just change the body background color so you can see that it works. You can use any CSS you'd like.
body {
background-color: #DDD;
}
You've just successfully created a derived theme. This needs to be enabled in the admin "themes" panel, and then anybody on the site can use it by selecting it in Settings->Display Settings as their default theme.
**Lesson 2**
If you want to use the redbasic schemas for your derived theme, you have to do a bit more.
Do everything as above, but don't create view/theme/mytheme/php/style.php, but copy instead view/theme/redbasic/php/style.php to view/theme/mytheme/php/style.php. Modify that file and remove (or comment out) these two lines:
if(local_channel() && App::$channel && App::$channel['channel_theme'] != 'redbasic')
set_pconfig(local_channel(), 'redbasic', 'schema', '---');
Also add this line at the bottom:
echo @file_get_contents('view/theme/mytheme/css/style.css');
To show the schema selector you have to copy view/theme/redbasic/tpl/theme_settings.tpl to view/theme/mytheme/tpl/theme_settings.tpl. Modify that file and replace the lines:
{{if $theme == redbasic}}
{{include file="field_select.tpl" field=$schema}}
{{/if}}
with:
{{include file="field_select.tpl" field=$schema}}

View File

@@ -0,0 +1,140 @@
## Creating a Derived Theme
In this tutorial, we will learn how to make a derived theme. This is a theme that takes most of the settings from its "parent" theme and lets you change a few things to your liking without creating an entire theme package. This is a good starting point if you just want to change a few things from an existing theme, for example to give your hub a bit of personalised appearance.
We will use the standard 'redbasic' theme for our parent theme in this tutorial. You may have to make some adaptions if you want to use another theme as your base, but the process will be the same.
### Lesson 1 Getting started
To create a derived theme, first choose a name. For our example we'll call our theme 'mytheme'. Hopefully you'll be a bit more creative. But throughout this document, wherever you see 'mytheme', replace that with the name you chose.
#### Directory Structure
First you need to create a theme directory structure. We'll keep it simple. We need a php directory and a css directory. Here are the Unix/Linux commands to do this. Assume that 'mywebsite' is your top level $projectname folder.
```
$ cd mywebsite
$ mkdir view/theme/mytheme
$ mkdir view/theme/mytheme/css
$ mkdir view/theme/mytheme/php
```
#### The Theme Info file
Great. Now we need a couple of files. The first one is your theme info file, which describes the theme.
It will be called `view/theme/mytheme/php/theme.php` (clever name huh?)
Inside it, put the following information - edit as needed
```php
<?php
/*
* Name: Mytheme
* Description: Sample Derived theme
* Version: 1.0
* Author: Your Name <your fedi handle (optional)>
* Extends: redbasic
*/
```
The top comment in the file makes up the information that is used by $Projectname for the information about the theme. Each of the lines is a kayword followed by a colon and a value. Notice the last line in the comment: `Extends: redbasic`. This is what tells $Projectname that this is a derived theme, and that it should get any elements missing from this theme from the parent theme. In this case we will be extending the theme 'redbasic'.
We don't need any further code in this file for now, so just leave it empty.
#### The PCSS file and style.css
Now create another file. We call this a PCSS file, but it's really a PHP file.
The file is called `view/theme/mytheme/php/style.php`.
In it, put the following:
```php
<?php
define('MY_THEME_ROOT', dirname(__DIR__));
define('PROJECT_THEME_ROOT', PROJECT_BASE . '/view/theme');
require_once(PROJECT_THEME_ROOT . '/redbasic/php/style.php');
echo file_get_contents(MY_THEME_ROOT . '/css/style.css');
```
That's it. This tells the software to read the PCSS information for the redbasic theme first, and then read our CSS file which will just consist of changes we want to make from our parent theme (redbasic).
Now create the actual CSS file for your theme. Put it in `view/theme/mytheme/css/style.css` (where we just told the software to look for it). For our example, we'll just change the body background colour so you can see that it works. You can use any CSS you'd like.
```css
body {
background-color: #DDD;
}
```
You've just successfully created a derived theme. This needs to be enabled in the admin "themes" panel, and then anybody on the site can use it by selecting it in Settings->Display Settings as their default theme.
#### Theme configuration and schemas
If you want to keep the various configuration options for the derived theme, you have to add a configuration class for your child theme.
Add another file, this time in `view/theme/mytheme/php/config.php`. Add the following content to it:
```php
<?php
namespace Zotlabs\Theme;
require_once 'view/theme/redbasic/php/config.php';
class MythemeConfig extends RedbasicConfig {
}
```
We leave the class itself empty, so that it will simply inherit the configuration and settings from the parent theme, in this case Redbasic. This will automatically make the settings and scheme selection from Redbasic available to your child theme as well.
### Lesson 2 - Customizing the theme
#### Adding custom styles
In the previous lesson we added a simple style in the `view/theme/mytheme/css/style.css` file, just so that we could see the that our derived theme was active.
We can of course add any styles we want to that file, and since we load it _after_ the parent theme's styles, we are able to override the styles provided by the parent theme, or by the $Projectname core. There's a lot that can be done with styling alone, and for many this will be all that you need to customize the theme to your liking.
#### Overriding templates
In some cases, overriding CSS styles may not be enough to get exactly the result you want from your theme. In these cases we can also override the templates used to build the HTML of the web pages itself.
Templates are the building blocks of the $Projectname web UI. They can be complex and contain the entire UI for a module, or simple building blocks like an input field or a button.
In this example we will modify the templates for checkboxes, changing them from the default toggle buttons used by Redbasic to a standard HTML checkbox.
This is what it looked like before the change:
![Before the change - toggle buttons](/help/en/tutorials/pic/override-template-before.png)
The original template is located in `view/tpl/field_checkbox.tpl`. So the first thing we do is to copy this file into our theme's template directory, that is `view/theme/mytheme/tpl/field_checkbox.tpl`. Then change it as you want. For this example, we change it to the following:
```html
<div id="{{$field.0}}_container" class="clearfix checkbox mb-3">
<input
type="checkbox"
name="{{$field.0}}"
id="id_{{$field.0}}"
value="1"
{{if $field.2}}checked="checked"{{/if}}
{{if $field.5}}{{$field.5}}{{/if}}
>
<label for="id_{{$field.0}}">
{{$field.1}}{{if $field.6}}<sup class="required zuiqmid"> {{$field.6}}</sup>{{/if}}
</label>
<div class="form-text text-muted">{{$field.3}}</div>
</div>
```
When looking for templates, $Projectname will first look for one in the theme's template directory, then (if relevant) in the parent theme's directory, and finally it will look in the `view/tpl` directory for the system defines templates. It will use the first template found.
This means that from now on, our theme's checkbox template will be used whenever the UI wants to display a checkbox.
This is how it looks with the change:
![After the change - normal checkboxes](/help/en/tutorials/pic/override-template-after.png)
Also note that files should be places in subdirectories under our theme directory that matches the extension of the file. So `*.css` files should be in the `view/theme/mytheme/css` directory, `*.php` files in the `view/theme/mytheme/php` directory, and `*.tpl` files in the `view/theme/mytheme/tpl` directory.

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -12,6 +12,6 @@ The content of custom_emojis.json should look as follows:
"another_emoji": {
"shortname": ":another_emoji:",
"filepath": "images/emoji/custom/another_emoji.png"
},
}
}
```
```

View File

@@ -2634,24 +2634,22 @@ function attach_move($channel_id, $resource_id, $new_folder_hash, $newname = '',
dbesc($resource_id)
);
if ($recurse) {
foreach($ps as $p) {
q("update photo set album = '%s', filename = '%s', os_path = '%s', display_path = '%s', content = '%s'
where resource_id = '%s' and imgscale = %d and uid = %d",
dbesc($newalbumname),
dbesc($filename),
dbesc($x['os_path']),
dbesc($x['path']),
dbescbin($newstorepath . ((intval($p['imgscale']) > 0) ? '-' . $p['imgscale'] : '')),
dbesc($resource_id),
intval($p['imgscale']),
intval($channel_id)
);
foreach($ps as $p) {
q("update photo set album = '%s', filename = '%s', os_path = '%s', display_path = '%s', content = '%s'
where resource_id = '%s' and imgscale = %d and uid = %d",
dbesc($newalbumname),
dbesc($filename),
dbesc($x['os_path']),
dbesc($x['path']),
dbescbin($newstorepath . ((intval($p['imgscale']) > 0) ? '-' . $p['imgscale'] : '')),
dbesc($resource_id),
intval($p['imgscale']),
intval($channel_id)
);
// the original should have been copied already
if (intval($p['imgscale']) > 0) {
rename($oldstorepath . '-' . $p['imgscale'], $newstorepath . '-' . $p['imgscale']);
}
// the original should have been copied already
if ($recurse && intval($p['imgscale']) > 0) {
rename($oldstorepath . '-' . $p['imgscale'], $newstorepath . '-' . $p['imgscale']);
}
}
}

View File

@@ -240,7 +240,7 @@ function comments_are_now_closed($item) {
return false;
}
function item_normal($profile_uid = null, $prefix = 'item') {
function item_normal($profile_uid = null, $prefix = 'item', $type = ITEM_TYPE_POST) {
if ($profile_uid === null) {
$profile_uid = App::$profile['profile_uid'] ?? App::$profile_uid ?? null;
}
@@ -248,7 +248,7 @@ function item_normal($profile_uid = null, $prefix = 'item') {
$uid = local_channel();
$is_owner = ($uid && intval($profile_uid) === $uid);
$sql = " and $prefix.item_hidden = 0 and $prefix.item_type = 0 and $prefix.item_deleted = 0
$sql = " and $prefix.item_hidden = 0 and $prefix.item_type = $type and $prefix.item_deleted = 0
and $prefix.item_unpublished = 0 and $prefix.item_pending_remove = 0";
if ($is_owner) {
@@ -3849,7 +3849,7 @@ function item_expire($uid,$days,$comment_days = 7) {
$sql_extra = ((intval($expire_network_only)) ? " AND item_wall = 0 " : "");
$expire_limit = Config::Get('system','expire_limit', 1000);
$expire_limit = Config::Get('system','expire_limit', 100);
$item_normal = item_normal();
@@ -4002,7 +4002,7 @@ function drop_item($id, $stage = DROPITEM_NORMAL, $force = false, $uid = 0, $obs
// activity and delete it. And vice versa.
function drop_related($item, $stage = DROPITEM_NORMAL, $force = false, $uid = 0, $observer_hash = '', $expire = false, $recurse = false) {
$allRelated = q("select * from item where parent_mid = '%s' and uid = %d",
$allRelated = q("select id, mid, verb, tgt_type, obj from item where parent_mid = '%s' and uid = %d",
dbesc($item['parent_mid']),
intval($item['uid'])
);
@@ -4198,25 +4198,20 @@ function delete_item_lowlevel($item, $stage = DROPITEM_NORMAL) {
*/
function first_post_date($uid, $wall = false) {
$sql_extra = '';
switch(App::$module) {
case 'articles':
$sql_extra .= " and item_type = 7 ";
$item_normal = " and item.item_hidden = 0 and item.item_type = 7 and item.item_deleted = 0
and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_pending_remove = 0
and item.item_blocked = 0 ";
break;
case 'channel':
$sql_extra = " and item_wall = 1 ";
default:
$item_normal = item_normal();
break;
}
$r = q("select id, created from item
where uid = %d and id = parent $item_normal $sql_extra
order by created asc limit 1",
where uid = %d and id = parent $item_normal
order by created limit 1",
intval($uid)
);
@@ -5365,17 +5360,18 @@ function set_activity_mid($string) {
* including activity counts.
* @param int $id
* @param int $parent
* @param int $type (optional) - defaults to ITEM_TYPE_POST
*/
function item_by_item_id(int $id, int $parent): array
function item_by_item_id(int $id, int $parent, int $type = ITEM_TYPE_POST): array
{
if (!$id && !$parent && !local_channel()) {
return [];
}
$item_normal_sql = item_normal();
$item_normal_sql = item_normal(type: $type);
$reaction = item_reaction_sql($parent);
$reaction = item_reaction_sql($parent, type: $type);
$reaction_cte_sql = $reaction['cte'];
$reaction_select_sql = $reaction['select'];
$reaction_join_sql = $reaction['join'];
@@ -5406,9 +5402,10 @@ function item_by_item_id(int $id, int $parent): array
* @param null|array $thr_parents (optional) - thr_parent mids which will be included
* @param string $permission_sql (optional) - SQL as provided by item_permission_sql() from the calling module
* @param bool $blog_mode (optional) - if set to yes only the parent items will be returned
* @param int $type (optional) - defaults to ITEM_TYPE_POST
*/
function items_by_parent_ids(array $parents, null|array $thr_parents = null, string $permission_sql = '', bool $blog_mode = false): array
function items_by_parent_ids(array $parents, null|array $thr_parents = null, string $permission_sql = '', bool $blog_mode = false, int $type = ITEM_TYPE_POST): array
{
if (!$parents) {
return [];
@@ -5416,7 +5413,7 @@ function items_by_parent_ids(array $parents, null|array $thr_parents = null, str
$ids = ids_to_querystr($parents, 'item_id');
$thread_allow = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true));
$item_normal_sql = item_normal();
$item_normal_sql = item_normal(type: $type);
$limit = $thread_allow ? 3 : 1000;
$thr_parent_sql = (($thread_allow) ? " AND item.thr_parent = item.parent_mid " : '');
@@ -5426,7 +5423,7 @@ function items_by_parent_ids(array $parents, null|array $thr_parents = null, str
$thr_parent_sql = " AND item.thr_parent IN (" . protect_sprintf($thr_parent_str) . ") ";
}
$reaction = item_reaction_sql($ids, $permission_sql, 'final_selection', $blog_mode);
$reaction = item_reaction_sql($ids, $permission_sql, 'final_selection', $blog_mode, $type);
$reaction_cte_sql = $reaction['cte'];
$reaction_select_sql = $reaction['select'];
$reaction_join_sql = $reaction['join'];
@@ -5504,11 +5501,12 @@ function items_by_parent_ids(array $parents, null|array $thr_parents = null, str
* @param string $ids
* @param string $permission_sql (optional) - SQL provided by item_permission_sql()
* @param string $join_prefix (optional) - prefix for the join part defaults to 'item'
* @param int $type (optional) - defaults to ITEM_TYPE_POST
*/
function item_reaction_sql(string $ids, string $permission_sql = '', string $join_prefix = 'item', bool $blog_mode = false): array
function item_reaction_sql(string $ids, string $permission_sql = '', string $join_prefix = 'item', bool $blog_mode = false, int $type = ITEM_TYPE_POST): array
{
$item_normal_sql = item_normal();
$item_normal_sql = item_normal(type: $type);
$observer = get_observer_hash();
$verbs = [
@@ -5691,7 +5689,7 @@ function item_activity_xchans(string $mid, int $parent, string $verb): array
);
$owner_uid = intval($parent_item[0]['uid']);
$item_normal = item_normal($owner_uid);
$item_normal = item_normal($owner_uid, type: $parent_item[0]['item_type']);
if (local_channel() === $owner_uid) {
$ret = q("SELECT item.id, item.item_blocked, xchan.xchan_hash, xchan.xchan_name as name, xchan.xchan_url as url, xchan.xchan_photo_s as photo FROM item

View File

@@ -769,7 +769,8 @@ function get_theme_info($theme){
'experimental' => false,
'unsupported' => false,
'theme_color' => '',
'background_color' => ''
'background_color' => '',
'extends' => '',
);
if(file_exists("view/theme/$theme/experimental"))
@@ -1077,10 +1078,11 @@ function theme_include($file, $root = '') {
$root = $root . '/';
$theme_info = App::$theme_info;
if(array_key_exists('extends',$theme_info))
$parent = $theme_info['extends'];
else
if(empty($theme_info['extends'])) {
$parent = 'NOPATH';
} else {
$parent = $theme_info['extends'];
}
$theme = Zotlabs\Render\Theme::current();
$thname = $theme[0];

View File

@@ -190,7 +190,8 @@ function tagadelic($uid, $count = 0, $authors = '', $owner = '', $flags = 0, $re
// Fetch tags
$r = q("select term, count(term) as total from term left join item on term.oid = item.id
where term.uid = %d and term.ttype = %d
and otype = %d and item_type = %d
and term.otype = %d and item.item_type = %d
and item.id = item.parent
$sql_options $item_normal
group by term order by total desc %s",
intval($uid),
@@ -322,12 +323,14 @@ function pubtagblock($net,$site,$limit,$recent = 0,$safemode = 1, $type = TERM_H
$link = z_root() . '/pubstream';
if($r) {
$o = '<div class="tagblock widget"><h3>' . (($recent) ? t('Trending') : t('Tags')) . '</h3><div class="tags" align="center">';
foreach($r as $rr) {
$o .= '<span class="tag'.$rr[2].'">#</span><a href="'.$link . '?tag=' . urlencode($rr[0]).'" class="tag'.$rr[2].'">'.$rr[0].'</a> ' . "\r\n";
}
$o .= '</div></div>';
if ($r) {
$url = $link . '?tag=';
$tpl = get_markup_template('tagcloud.tpl');
$o .= replace_macros($tpl, [
'$title' => t('Trending'),
'$baseurl' => $url,
'$tags' => $r,
]);
}
return $o;
@@ -368,9 +371,10 @@ function pub_tagadelic($net, $site, $limit, $recent, $safemode, $type) {
$arr = [
"SELECT term, count(term) AS total FROM term LEFT JOIN item ON term.oid = item.id
WHERE term.ttype = %d
AND otype = %d
AND item_type = %d
AND item_private = 0
AND term.otype = %d
AND item.item_type = %d
AND item.item_private = 0
AND item.id = item.parent
$uids $item_normal $site_firehose_sql $sql_extra
GROUP BY term ORDER BY total DESC %s",
intval($type),
@@ -483,11 +487,14 @@ function wtagblock($uid,$count = 0,$authors = '',$owner = '', $flags = 0,$restri
intval($uid)
);
$o = '<div class="tagblock widget"><h3>' . t('Tags') . '</h3><div class="tags" align="center">';
foreach($r as $rr) {
$o .= '<span class="tag' . $rr[2] . '">#</span><a href="channel/' . $c[0]['channel_address'] . '?f=&tag=' . urlencode($rr[0]).'" class="tag'.$rr[2].'">'.$rr[0].'</a> ' . "\r\n";
}
$o .= '</div></div>';
$channel = App::get_channel();
$url = z_root() . '/channel/' . $channel['channel_address'].'/?f=&tag=';
$tpl = get_markup_template('tagcloud.tpl');
$o .= replace_macros($tpl, [
'$title' => t('Tags'),
'$baseurl' => $url,
'$tags' => $r,
]);
}
return $o;
@@ -504,11 +511,13 @@ function catblock($uid,$count = 0,$authors = '',$owner = '', $flags = 0,$restric
intval($uid)
);
$o = '<div class="tagblock widget"><h3>' . t('Categories') . '</h3><div class="tags" align="center">';
foreach($r as $rr) {
$o .= '<a href="channel/' . $c[0]['channel_address']. '?f=&cat=' . urlencode($rr[0]).'" class="tag'.$rr[2].'">'.$rr[0].'</a> ' . "\r\n";
}
$o .= '</div></div>';
$url = z_root() . '/channel/' . $c[0]['channel_address'].'/?f=&cat=';
$tpl = get_markup_template('tagcloud.tpl');
$o .= replace_macros($tpl, [
'$title' => t('Categories'),
'$baseurl' => $url,
'$tags' => $r,
]);
}
return $o;
@@ -563,13 +572,14 @@ function dir_tagblock($link,$r) {
$r = App::$data['directory_keywords'] ?? [];
if($r) {
$o = '<div class="dirtagblock widget"><h3>' . t('Keywords') . '</h3><div class="tags" align="center">';
foreach($r as $rr) {
$o .= '<a href="'.$link .'/' . '?f=&keywords=' . urlencode($rr['term']).'" class="tag'.$rr['normalise'].'" rel="nofollow" >'.$rr['term'].'</a> ' . "\r\n";
}
$o .= '</div></div>';
$url = $link . '/?f=&keywords=';
$tpl = get_markup_template('dirtagcloud.tpl');
$o .= replace_macros($tpl, [
'$title' => t('Keywords'),
'$baseurl' => $url,
'$tags' => $r,
]);
}
return $o;
}

View File

@@ -942,20 +942,20 @@ function updateConvItems(mode, data) {
mediaPlaying = event.type === 'playing';
}
imagesLoaded(document.querySelectorAll('.wall-item-body img, .wall-photo-item img'), function () {
if (bParam_mid && mode === 'replace') {
scrollToItem();
}
else {
collapseHeight();
}
});
if (bParam_mid && mode === 'replace') {
scrollToItem();
}
// reset rotators and cursors we may have set before reaching this place
// A slight delay to give the browser time to render images.
// Otherwise height calculation might not be accurate.
setTimeout(collapseHeight, 10);
// Reset rotators and cursors we may have set before reaching this place
let pageSpinner = document.getElementById("page-spinner");
if (pageSpinner) {
pageSpinner.style.display = 'none';
}
let profileJotTextLoading = document.getElementById("profile-jot-text-loading");
if (profileJotTextLoading) {
profileJotTextLoading.style.display = 'none';
@@ -971,6 +971,7 @@ function imagesLoaded(elements, callback) {
let loadedCount = 0;
let totalImages = 0;
let timeoutId;
let timedOut = false;
const timeout = 10000;
const processed = new Set(); // Use a Set for efficient lookup
@@ -982,6 +983,10 @@ function imagesLoaded(elements, callback) {
}
function checkComplete(src) {
// If preloading timed out make sure to not call the callback again
// in case a load event listener fires later.
if (timedOut) return;
// Skip processing if image has already been processed
if (processed.has(src)) return;
@@ -1029,7 +1034,9 @@ function imagesLoaded(elements, callback) {
// Set timeout for the loading process
timeoutId = setTimeout(() => {
console.warn(`Image loading timed out after ${timeout}ms`);
document.getElementById('image_counter').innerText = '';
callback(false);
timedOut = true;
}, timeout);
// Iterate through images to add load and error event listeners
@@ -1771,6 +1778,7 @@ function doprofilelike(ident, verb) {
function doreply(parent, ident, owner, hint) {
const modal = new bootstrap.Modal('#reactions');
const modal_container = document.getElementById('reactions')
const modal_content = document.getElementById('reactions_body');
const modal_title = document.getElementById('reactions_title');
const modal_action = document.getElementById('reactions_action');
@@ -1779,23 +1787,28 @@ function doreply(parent, ident, owner, hint) {
modal_title.innerHTML = hint;
const preview = document.getElementById('comment-edit-preview-' + parent.toString());
preview.innerHTML = '';
if (preview) preview.innerHTML = '';
const form_container = document.getElementById('comment-edit-wrapper-' + parent.toString());
// Get the form element by ID
const form = document.getElementById('comment-edit-wrapper-' + parent.toString());
const form = document.getElementById('comment-edit-form-' + parent.toString());
if (!form) return;
modal_content.innerHTML = '';
modal_content.append(form);
modal_content.append(preview);
// Set the value of the input named 'parent'
const parentInput = form.querySelector('input[name=parent]');
if (parentInput) {
parentInput.value = ident;
}
// Find the submit button and update its HTML
const submitBtn = form.querySelector('button[type=submit]');
if (submitBtn) {
const btnText = submitBtn.innerHTML.replace(/<[^>]*>/g, '').trim();
submitBtn.innerHTML = '<i class="bi bi-arrow-90deg-left"></i> ' + btnText;
@@ -1808,6 +1821,7 @@ function doreply(parent, ident, owner, hint) {
// Check if the selection is inside the correct element
let isInSel = false;
const anchorNode = window.getSelection().anchorNode;
if (anchorNode) {
let node = anchorNode.nodeType === 3 ? anchorNode.parentNode : anchorNode;
while (node) {
@@ -1821,15 +1835,26 @@ function doreply(parent, ident, owner, hint) {
modal.show();
modal_container.addEventListener('hide.bs.modal', event => {
// move form back to where it was
form_container.append(form);
form_container.append(preview);
});
// Set the textarea value
const textarea = form.querySelector('textarea');
if (textarea) {
let commentBody = localStorage.getItem('comment_body-' + ident);
if (commentBody) {
textarea.value = commentBody;
}
else {
textarea.value = "@{" + owner + "}" + ((!isInSel || quote.length === 0) ? " " : "\n[quote]" + quote + "[/quote]\n");
textarea.value = '@{' + owner + '} ';
if (quote && isInSel) {
textarea.value += "\n[quote]" + quote + "[/quote]\n";
}
}
textarea.focus();
@@ -2032,10 +2057,11 @@ function post_comment(id) {
$('body').css('cursor', 'wait');
$("#comment-preview-inp-" + id).val("0");
if(typeof conv_mode == typeof undefined)
if (typeof conv_mode == typeof undefined) {
conv_mode = '';
}
var form_data = $("#comment-edit-form-" + id).serialize();
const form_data = $("#comment-edit-form-" + id).serialize();
$.post(
"item",
@@ -2055,12 +2081,19 @@ function post_comment(id) {
$("#comment-edit-text-" + id).val('').blur().attr('placeholder', aStr.comment);
$('#wall-item-sub-thread-wrapper-' + data.thr_parent_id).append(data.html);
const comment = document.getElementById('wall-item-content-wrapper-' + data.id);
comment.classList.add('item-highlight-fade');
comment.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
updateRelativeTime('.autotime');
$('body').css('cursor', 'unset');
collapseHeight();
commentBusy = false;
var tarea = document.getElementById("comment-edit-text-" + id);
const tarea = document.getElementById("comment-edit-text-" + id);
if (tarea) {
commentClose(tarea, id);
$(document).off( "click.commentOpen");

View File

@@ -1,9 +1,7 @@
<div class="float-start me-2">
<a href="{{$href}}" title="{{$link_label}}" target="_blank">
<img src="{{$img_src}}" class="rounded" style="width: 3rem; height: 3rem;" />
</a>
</div>
<div class="m-1">
<div class="text-truncate h3 m-0"><strong>{{if $is_group}}<i class="bi bi-chat-quote" title="{{$group_label}}"></i> {{/if}}{{$name}}</strong></div>
<div class="text-truncate text-muted">{{$addr}}</div>
</div>
<a href="{{$href}}" title="{{$link_label}}" target="_blank">
<img src="{{$img_src}}" class="rounded menu-img-3" />
<div>
<div class="text-truncate h3 m-0"><strong>{{if $is_group}}<i class="bi bi-chat-quote" title="{{$group_label}}"></i> {{/if}}{{$name}}</strong></div>
<div class="text-truncate text-muted">{{$addr}}</div>
</div>
</a>

10
view/tpl/dirtagcloud.tpl Normal file
View File

@@ -0,0 +1,10 @@
<div class="dirtagblock widget">
<h3>{{$title}}</h3>
<div class="tags text-center">
{{foreach $tags as $tag}}
<span class="tag tag{{$tag.normalise}}">#</span><a href="{{$baseurl}}{{$tag.term}}" class="tag tag{{$tag.normalise}}" rel="nofollow">{{$tag.term}}</a>
{{/foreach}}
</div>
</div>

View File

@@ -40,7 +40,7 @@ function toggle_posted_date_button() {
{{/foreach}}
{{if $cutoff}}
</div>
<button class="btn btn-outline-secondary btn-sm" onclick="toggle_posted_date_button(); return false;"><i id="posted-date-icon" class="bi bi-chevron-down"></i></button>
<button class="btn btn-outline-secondary btn-sm border-0" onclick="toggle_posted_date_button(); return false;"><i id="posted-date-icon" class="bi bi-chevron-down"></i></button>
{{/if}}
</ul>
</div>

10
view/tpl/tagcloud.tpl Normal file
View File

@@ -0,0 +1,10 @@
<div class="tagblock widget">
<h3>{{$title}}</h3>
<div class="tags text-center">
{{foreach $tags as $tag}}
<span class="tag{{$tag.2}}">#</span><a href="{{$baseurl}}{{$tag.0}}" class="tag{{$tag.2}}">{{$tag.0}}</a>
{{/foreach}}
</div>
</div>

View File

@@ -23,4 +23,22 @@
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<div class="modal" id="reactions" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="reactions_title"></h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button>
</div>
<div class="modal-header" id="reactions_action">
</div>
<div class="modal-body list-group" id="reactions_body">
{{$wait}}
</div>
<div class="ps-3 pe-3" id="reactions_extra_top"></div>
<div class="ps-3 pe-3" id="reactions_extra_middle"></div>
<div class="ps-3 pe-3" id="reactions_extra_bottom"></div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
{{/if}}