News:

Bored?  Looking to kill some time?  Want to chat with other SMF users?  Join us in IRC chat or Discord

Main Menu

Threaded view

Started by Tilla, August 13, 2008, 09:07:54 PM

Previous topic - Next topic

Tilla

I am wondering if it is possible for you guys to add threaded view in this forum software? I am in a community where users are accustomed to threaded view and as much as they like this new SMF forum, some are asking if they could get threaded view feature in the forum instead of the current flat view. Is there a possibility that this could be added? Hopefully I have not overlooked this feature in the current version (Version 2.0 3.1 beta).

BTW, this is a very good product as currently designed.

ccbtimewiz

What exactly is "threaded" view?

Dannii

I don't believe there are any plans to have a threaded view as it would require massive changes to a lot of the codebase.
"Never imagine yourself not to be otherwise than what it might appear to others that what you were or might have been was not otherwise than what you had been would have appeared to them to be otherwise."

Tilla

Quote from: ccbtimewiz on August 13, 2008, 09:08:49 PM
What exactly is "threaded" view?
Check the attached picture of a  vBulletin forum which is displaying in threaded view. The user has the option to switch to flat view as used here.




ccbtimewiz

Quote from: Tilla on August 13, 2008, 09:30:02 PM
Quote from: ccbtimewiz on August 13, 2008, 09:08:49 PM
What exactly is "threaded" view?
Check the attached picture of a  vBulletin forum which is displaying in threaded view. The user has the option to switch to flat view as used here.

Yeah... it would require massive changes to a lot of the codebase as Dannii said. Maybe you can request this as a mod?

Tilla

OK, I will see if I can request it as a mod. I am sure you can see the benefit of a threaded view?

One of the reasons I like it is that when you are inside a particular thread, people can be replying to specific persons and that discussion is kept together. This means you do not have to read all the discussions to keep up with someone who is responding to your post or you to them, unlike the flat view as used by this forum.

This is the only thing I see missing from this SMF product. I am sure there are others, but the only change I would like to see for now.

Dannii

I actually find it very hard to use. I much prefer a flat forum with lots of quoting.
"Never imagine yourself not to be otherwise than what it might appear to others that what you were or might have been was not otherwise than what you had been would have appeared to them to be otherwise."

karlbenson

The issue with it is that is would be very bloaty.

The sections affected would essentially need duplicating and rewriting completely.
If you tried to work it into existing functions it would be very heavy and inefficient and slow.

For that reason I find it more likely as a mod.

Nao 尚

Not that bloaty, actually. Just a little painful here and there. The blottiest things have to be the extra Javascript (less than a kilobyte), and the extra id_parent field in the smf_messages table. (I don't like adding too much here...)

If you remember, I added that particular feature to my board some time ago -- http://www.simplemachines.org/community/index.php?topic=212576.0 (I even posted a screenshot.)
After the last message in that topic, I modified the Quick Reply feature to actually add a Quick Reply box under EVERY message. Which made it much easier to "build" an actual threaded view.
It worked, really. Very well. I've kept a copy of all the changes I made.

Only, I posted a poll, asking my viewers their feelings about the feature, and only two people shared their enthusiasm -- and that's because they're originally from LiveJournal where threaded view is all over the place.
Everybody else either said they didn't care, or reacted strongly against the feature, saying it would make it more complicated to follow a discussion.
While a threaded view is desireable when you're viewing a topic once and long after it was created (as a guest, from a Google search, etc.), it is not the best possible way to discuss when the topic is active.
Of course, there's always the possibility of not showing any posts, only a complete list of indented subjects, with a "new" icon next to the messages you haven't read yet, and a single click to get to that particular message. But even if using Ajax to retrieve them, that would still require a LOT of clicking, and keeping track of all unread messages, instead of "last unread message"! Oh my, the agony...

Another advantage of threaded views is that you can split a topic with a single click on the parent message that generated a new discussion. That was one of my main reasons for implementing it in the first place, actually.
But threaded view is more or less a thing of the past. More and more people are getting used to flat view. SMF supports backlinks to the original post from within a quote -- as long, of course, as the poster doesn't remove the link itself.

So I ended up removing my code. It just took a few minutes and, although the threaded code wasn't really "in the way", I'd rather not keep unused code in my already messy additional code.

If anyone wants the code, though... Feel free to ask. I'm never going to make a mod out of it anyway (making mods is not my cup of tea), so I might just as well share the code itself, in case someone wants to make a mod.
I will not make any deals with you. I've resigned. I will not be pushed, filed, stamped, indexed, briefed, debriefed or numbered.

Aeva Media rocks your life.

Dannii

Trust Nao to do the impossible :P
"Never imagine yourself not to be otherwise than what it might appear to others that what you were or might have been was not otherwise than what you had been would have appeared to them to be otherwise."

Nao 尚

Lol, it was far from "impossible". The only real thing that I deemed impossible at first, was to add Quick Reply to all posts, because of the overlapping forms. In the end, adding them on the fly via Javascript works fine.

Heck, I should have posted my changes last month, when I *removed* them from my code... It would have been faster and easier than tracking down the changes between an older version of my code and a newer one ;)
I will not make any deals with you. I've resigned. I will not be pushed, filed, stamped, indexed, briefed, debriefed or numbered.

Aeva Media rocks your life.

Dragooon

I am quite interested in the workings of it. It'd be great if you share it :)

Nao 尚

Okay, let's go to work...!!

First of all, add a "id_parent" field to "smf_messages". It shall have the same format as "id_msg", since it will store the same kind of information...

In Post.php, BEFORE "// Start the main table.", add:

if (!empty($_REQUEST['quote']))
echo '
<input type="hidden" name="quote" value="', (int) $_REQUEST['quote'], '" />';


Now go to the Display template...

Add this around the beginning. (Before "is_poll")

if (!empty($context['threaded']))
echo '
<div class="tborder" id="quickreplybox' , $message['id'], '">
<h3 class="titlebg headerpadding">
<a href="javascript:oQuickReply.swap();">
<img src="' . $settings['images_url'] . '/expand.gif" alt="+" id="quickReplyExpand', $message['id'], '" />
</a>
<a href="javascript:oQuickReply.swap(' . $message['id'] . ');">' . $txt['quick_reply'] . '</a>
</h3>
<div class="smallpadding windowbg" id="quickReplyOptions', $message['id'], '" style="display: none">
</div>
</div>';


After "$ignoredMsgs = array();", add:

$increm_list = array();
$increm_list[0] = -20;
$increm_list[$context['first_message']] = -20;


After the "if ($message['id'] != $context['first_message'])" echo sequence, add:

if (!empty($context['threaded']))
{
$increm = $increm_list[$message['parent']] + 20;
$increm_list[$message['id']] = $increm;
}

$sty = $message['counter'] == $context['start'] ? 'margin-top: 0' : '';
$sty .= !empty($increm) ? (!empty($sty) ? '; ' : '') . 'display: none; margin-left: ' . $increm . 'px' : '';

if (!empty($increm))
echo '
<div style="margin-left: ' . $increm . 'px"><a href="javascript:void(0)"
onclick="n = document.getElementById(\'mypost' . $message['id'] . '\'); if(n.style.display == \'none\') { n.style.display = \'block\'; } else { n.style.display = \'none\'; } return false;">R&eacute;ponse de '
. $message['member']['name'] . ' ' . (is_numeric($message['time'][0]) ? $txt['on'] : '') . ' ' . $message['time'] . '</a>' . ($message['new'] ? ' <img src="' . $settings['images_url'] . '/new.png" alt="New" border="0" />' : '') . '</div>';


(You'll have to replace the "new.png" link, as well as add language strings for "Réponse de" which is French for "Reply by... (author name)")

Now we're starting the main div for each message... Just increment them, something like that:

echo '
<div class="clearfix ', !$message['first_new'] ? 'topborder ' : '', $message['approved'] ? ($message['alternate'] == 0 ? 'windowbg' : 'windowbg2') : 'approvebg', ' largepadding"', !empty($sty) ? ' style="' . $sty . '" id="mypost' . $message['id'] . '"' : '', '>';


Now, right before the end of that main div, add this:

if (!empty($context['threaded']))
echo '
<div class="tborder" id="quickreplybox' , $message['id'], '">
<h3 class="titlebg headerpadding">
<a href="javascript:oQuickReply.swap();">
<img src="' . $settings['images_url'] . '/expand.gif" alt="+" id="quickReplyExpand', $message['id'], '" />
</a>
<a href="javascript:oQuickReply.swap(' . $message['id'] . ');">' . $txt['quick_reply'] . '</a>
</h3>
<div class="smallpadding windowbg" id="quickReplyOptions', $message['id'], '" style="display: none">
</div>
</div>';


Now add the final div.

I usually add a "unset($increm_list);" after the message loop.

In the Quick Reply <form> stuff, I add these among the variables:

<input type="hidden" name="quote" value="" />

Now, AFTER the quick reply code block, I add this:

$a = '';
if ($context['can_reply'] && !empty($options['display_quick_reply']) && !empty($context['threaded']))
{
$a = '
<div class="smallpadding floatleft" id="warning%msg%">
' . $txt['quick_reply_desc'] . ($context['is_locked'] ? '<p><strong>' . $txt['quick_reply_warning'] . '</strong>' : '') . ($context['oldTopicError'] ? '</p><strong>' . sprintf($txt['error_old_topic'] . $modSettings['oldTopicDays']) . '</strong>' : '') . '
</div>
<div>
' . ($context['can_reply_approved'] ? '' : '<em>' . $txt['wait_for_approval'] . '</em>') . '
<form action="' . $scripturl . '?action=post2" method="post" accept-charset="' . $context['character_set'] . '" name="postmodify" id="postmodify%msg%" onsubmit="submitonce(this);" style="margin: 0;">
<input type="hidden" name="topic" value="' . $context['current_topic'] . '" />
<input type="hidden" name="subject" value="' . $context['response_prefix'] . $context['subject'] . '" />
<input type="hidden" name="icon" value="xx" />
<input type="hidden" name="quote" value="%msg%" />
<input type="hidden" name="notify" value="' . ($context['is_marked_notify'] || !empty($options['auto_notify']) ? '1' : '0') . '" />
<input type="hidden" name="not_approved" value="' . !$context['can_reply_approved'] . '" />
<input type="hidden" name="goback" value="' . (empty($options['return_to_post']) ? '0' : '1') . '" />
<input type="hidden" name="num_replies" value="' . $context['num_replies'] . '" />
<textarea cols="75" rows="10" style="width: 95%; height: 150px;" name="message" tabindex="1"></textarea><br />
<input type="submit" name="post" value="' . $txt['post'] . '" onclick="return submitThisOnce(this);" accesskey="s" tabindex="2" />
<input type="submit" name="preview" value="' . $txt['preview'] . '" onclick="return submitThisOnce(this);" accesskey="p" tabindex="4" />';

if ($context['show_spellchecking'])
$a .= '
<input type="button" value="' . $txt['spell_check'] . '" onclick="spellCheck(\'postmodify\', \'message\');" tabindex="5" />';
$a .= '
<input type="hidden" name="sc" value="' . $context['session_id'] . '" />
<input type="hidden" name="seqnum" value="' . $context['form_sequence_number'] . '" />
</form>
</div>
</div>
</div>';
$a = str_replace('\'', '\\\'', $a);
$a = str_replace(array("\n","\r"), '', $a);
}


(a "from_qr" hidden value was added recently for Beta 4, I'm not sure it should be used here as well. Would have to check.)

Now, let's skip to the Quick Reply javascript...

Replace:
sJumpAnchor: "quickreply"

With:
sJumpAnchor: "quickreply"', (empty($context['threaded']) ? '' : ',
sThreaded: \'' . $a . '\''), '


Now, xml_topic.js

Add a new line after this first one:

this.bCollapsed = this.opt.bDefaultCollapsed;
this.bExbox = 0;


Add the first line (and linebreak) below before the two lines after:

document.forms.postmodify.quote.value = iMessageId;

// Doing it the XMLhttp way?
if (window.XMLHttpRequest)


The QuickReply swap function is half-rewritten, so it's best to simply reproduce it completely:

QuickReply.prototype.swap = function (destDiv)
{
if (typeof(destDiv) == 'undefined')
destDiv = '';

document.getElementById(this.opt.sImageId + destDiv).src = this.opt.sImagesUrl + "/" + (this.bCollapsed ? this.opt.sImageCollapsed : this.opt.sImageExpanded);
document.getElementById(this.opt.sContainerId + destDiv).style.display = this.bCollapsed ? '' : 'none';
if (this.bExbox > 0)
setInnerHTML(document.getElementById('quickReplyOptions' + this.bExbox), '');
setInnerHTML(document.getElementById('quickReplyOptions' + destDiv), this.opt.sThreaded.replace(/%msg%/g, destDiv));
this.bExbox = destDiv;
this.bCollapsed = !this.bCollapsed;
}


And finally (for now), we skip to Display.php...

Add this somewhere around the beginning:

function Tri_recursif($msg)
{
global $atrier, $ordre, $parent;

foreach ($atrier as $key => $msg2)
if ($parent[$msg2] == $msg)
{
$ordre[$key] = $msg2;
unset($atrier[$msg2]);
Tri_recursif($msg2);
}
}


(Feel free to rename this function to "recursive_sort" if you'd like.)

Add these to the globals in Display():

global $atrier, $ordre, $parent;

($atrier means "to be sorted", $ordre means "order", sorry for using French-language variable names, I didn't plan to release my code in the first place...)

After the "// Duplicate link!  Tell the robots not to link this." lines, add:

if ($_REQUEST['start'] == 'threaded')
{
$context['threaded'] = true;
$_REQUEST['start'] = -1;
}


BEFORE "// If they are viewing all the posts, show all the posts, otherwise limit the number.", add:

if (!empty($context['threaded']))
{
$context['messages_per_page'] = -1;
$context['page_index'] .= empty($modSettings['compactTopicPagesEnable']) ? '<b>' . $txt['all'] . '</b> ' : '[<b>' . $txt['all'] . '</b>] ';

// Set start back to 0...
$_REQUEST['start'] = 0;
}


After these three lines, add the final line in this code block:

// Find out if there is a post before that is double ;)
$context['last_user_id'] = 0;
$context['last_msn_id'] = 0;
$context['last_parent_id'] = 0;


In the SQL request that comes next, add id_parent:

SELECT id_member, id_msg, body, poster_email, id_parent

Before "if (!empty($row['body']))", add:

$context['last_parent_id'] = $row['id_parent'] == $context['topic_first_message'] ? 0 : (int) $row['id_parent'];

Next request, same thing, add id_parent:

SELECT id_msg, id_member, approved, id_parent

Add the third line in this code block:

$messages = array();
$posters = array();
$parent = array();


Immediately after that, replace the whole while() code block with this: (there are 3 extra lines)

while ($row = $smcFunc['db_fetch_assoc']($request))
{
if (empty($first_msg))
$first_msg = $row['id_msg'];
if (!empty($row['id_member']))
$posters[] = $row['id_member'];
$messages[] = $row['id_msg'];
$parent[$row['id_msg']] = $row['id_parent'] == $first_msg ? 0 : $row['id_parent'];
}


BEFORE "// Fetch attachments.", add:

if (!empty($context['threaded']))
{
$ordre = array();
$atrier = $messages;
sort($atrier);
Tri_recursif(0);
$atrier = array_keys($ordre);
unset($ordre);
}


Skip the next request, and the request after that should add id_parent:

smileys_enabled, poster_name, poster_email, approved, id_parent,

Now, in prepareDisplayContext(), add these to the globals:

global $atrier, $ordre;

BEFORE "// Attempt to get the next message.", add:

if (sizeof($atrier) > 0)
{
if ($counter >= sizeof($atrier))
return false;
@$smcFunc['db_data_seek']($messages_request, $atrier[$counter]);
}


BEFORE "// Compose the memory eat- I mean message array.", add:

$new_parent_id = $message['id_parent'] == $context['first_message'] ? 0 : $message['id_parent']; // should id be topic_first_message?


After the 'subject' and before 'time' (but it can be anywhere else), I added this to the array:

'parent' => $new_parent_id,

In the "doublepost" subarray (this is only used in the Merge DoublePost mod, and the reason for some of my otherwise useless code additions to Display.php, so you'll have to remove these manually for an actual mod!), replace the 'postok' line with:

'postok' => !empty($context['last_user_id']) && ($new_parent_id == 0) && ($context['last_parent_id'] == 0) && $Merge_MaxLimitLength && $context['last_user_id'] == (empty($message['id_member']) ? (empty($message['poster_email']) ? $message['poster_name'] : $message['poster_email']) : $message['id_member']) && (allowedTo('modify_any') || (allowedTo('modify_own') && $message['id_member'] == $user_info['id'])),

Before "return $output;", add this:

$context['last_parent_id'] = $message['id_parent'];




Okay, I think we're done...

- As you can see, it's not that big a deal in terms of additions. It can make for a very manageable mod.

- As I mentioned earlier, some of my code was added specifically for Merge DoublePost, and it doesn't work perfectly either. The problem with Merge DoublePost is that you want to make sure one can't merge two posts made in a row, if the posts belong to a different thread in the topic. This is very important. I would recommend on disabling Merge DoublePost if you intend to use Threaded view at all on your board. My code also needs some polishing on this. If you want to remove everything related to MDP, just look for $context['last_parent_id'], I think that's the only related variable.

- I know for a fact that there are a couple lines (small blocks?) of code which I removed AFTER my first uninstall of the mod, so it means they're not mentioned in this post. I will have to find them later. If you're having any problems with the integration, please post here and I will try my best to help you.
Edit -- I found another piece of code I had forgotten. Hopefully this was the thing I had also forgotten to remove the first time around. Look at the beginning of the post.
I will not make any deals with you. I've resigned. I will not be pushed, filed, stamped, indexed, briefed, debriefed or numbered.

Aeva Media rocks your life.


Nao 尚

Read again my first post in that page... (the first lines)

I've disabled the feature on my website, though, since most users disliked the whole concept (including me). But I believe karlbenson is now working on making my code into a mod. I don't know where he's up to right now, though.
I will not make any deals with you. I've resigned. I will not be pushed, filed, stamped, indexed, briefed, debriefed or numbered.

Aeva Media rocks your life.

karlbenson

I currently don't have the time to package it up I'm afraid.

Hopefully someone else will.

Nao 尚

I will not make any deals with you. I've resigned. I will not be pushed, filed, stamped, indexed, briefed, debriefed or numbered.

Aeva Media rocks your life.

青山 素子

If I find the time, I might look over it.

I haven't looked at the provided code in depth, so I might have missed it, but how are deleted posts that have children handled?
Motoko-chan
Director, Simple Machines

Note: Unless otherwise stated, my posts are not representative of any official position or opinion of Simple Machines.


Thantos

Don't forget spliting posts that have children.

Nao 尚

Quote from: Motoko-chan on September 18, 2008, 11:52:21 AM
If I find the time, I might look over it.

I haven't looked at the provided code in depth, so I might have missed it, but how are deleted posts that have children handled?
I don't remember ever addressing this, actually. I wasn't too sure whether to go the iMDb and LiveJournal way ("this post was deleted by its author", followed by the children still there), or just "reconnect" children posts with their parent's parent.

Thantos> Same here, never implemented (I'm not too familiar with the split code, so I left it for later, and it never happened). But this was actually one of my main reasons for wanting to make this threaded mode. Easy topic split by just splitting the main post and having all of the children come together naturally in the new topic.
I will not make any deals with you. I've resigned. I will not be pushed, filed, stamped, indexed, briefed, debriefed or numbered.

Aeva Media rocks your life.

Advertisement: