Adding a "skip to answer" button to a question
First some context:
I commented out the part of the QnA plugin that takes the answers from the db, pins them, and then removes them from the post count. So the answers are still in their original position but simply marked as "answer".
For the time being my objective was to add a "skip to answer" button in the post option of questions that have answers.(ideally before the "favorite" star)
That button will skip to the first answer for the question at hand.
This is what i got:
public function DiscussionController_BeforeDiscussionOptions_Handler($Sender) { if (strcasecmp($Sender->Data('Discussion.QnA'), 'Accepted') != 0) return; $Answer = Gdn::SQL()->GetWhere('Comment', array('DiscussionID' => $Sender->Data('Discussion.DiscussionID'), 'Qna' => 'Accepted'))->FirstRow(DATASET_TYPE_ARRAY); $AnswerID = $Answer['CommentID']; $DiscussionID = $Sender->Data('Discussion.DiscussionID'); $DiscussionName = $Sender->Data('Discussion.Name'); $DiscussionName = preg_replace('/\s+/', '-', $DiscussionName); //Not necessary but keeps in line with default formatting echo '<span><a href="'.Gdn_Format::text($DiscussionName).'#Comment_'.Gdn_Format::text($AnswerID).'">Skip to Answer</a></span>'; }
It works and does what i need. However, I got this from scraping code parts of plugins.
So there are things i don't understand completely.
Like what does this do, exactly: "->FirstRow(DATASET_TYPE_ARRAY)".
My take from this is that it gets the first row that it finds with the given search and adds it to the var as an array type.
Are there other possible returns?
Also, I did things in what seemed like the most logical choice for a first try, but is there a better way?
Maybe a more "vanilla way" of doing things?
I'm also not sure if there is a explicit need for Gdn_Format::text in this case, but I assume it's safer.
I defer to the expert knowledge of the vanilla-php masters here to help me out.
I'm a lot better than when I started, but I'm still learning a lot about vanilla everyday.
Feel free to correct me in any way, as stated I'm still learning.
Thanks in advance.
Comments
Don't change core files
At first: changing any files is bad. The "Vanilla way" is to make changes by adding code, not by removing lines.
The reason is quite simple: as soon as you change the Q&A plugin itself, you have to ensure that your changes do not get lost once you do an update.
Create your own plugin
That's why you should create your own plugin. Since it requires the Q&A plugin, make sure you have such a line in the PluginInfo array of your own plugin
'RequiredPlugins' => array('QnA' => '1.2'),
Prevent answers from being removed
As soon as your plugin is activated it should set the config setting "QnA.AcceptedAnswers.Filter" to
false
in order to prevent the answers from being removed from the normal comments. That's really easy. Put that in your new plugin (remember: don't change files from others)There are some method names with special meaning:
setup()
will automatically be called when the plugin gets enabled. The above saves a value to the config (you already guessed that from the functions name, I suppose)Add some CSS
But you still have the answers at the top, and I'm afraid to say that there is no obviously elegant way to hide the answers (but fear not, there is a nifty trick). The standard way to "solve" that would be to hide that part with CSS. You could create a /plugins/yourpluginname/design/yourpluginname.css file and use the style below to hide that answers.
Afterwards you would need to include that file whenever a discussion is displayed
Please bear with me, the next few words are kind of fuzzy...
..._render_before()
is another "special" method which is called as the first method for every page that is called, even before it is rendered. That is the method you use to add css and js files. It could bediscussionsController_render_before()
for every "www.yourforum.com/discussions/[recent|mine|participated|...]" page,discussionController_render_before()
for every "www.yourforum.com/discussion/DiscussionID/DiscussionTitle" page,categoryController_render_before()
...
I think you understand the idea.
base_render_before()
is special special. It will be called before every page.But really, the above sentences where not exact, they could be an introduction, but as soon as you have understood how MVC is done in Vanilla you would be able to get a better understanding.
A better solution
By hiding what you do not like to see with CSS you a) do not hide it before search engines and b) you send superfluous data from the server to the client. It would be far more elegant not to send anything at all.
You've changed the plugin and deleted the following lines:
The problem
There is no config setting in order to change something here. Next thing to think about would be if you could change what
fetchViewLocation
does. If it would return an empty string we would have reached the goal. Although you might be able to do something like that with a custom theme (I honestly don't know if that would be possible, but it would be one thing to think about), something like that would be overkill.Okay, so no config setting, no way to change the "target" of the include file - next thing would be to check the view. But the answers.php file is quite simple and also has no ways to change the output.
The solution!
But the reason why the view is loaded (i.e. the answers are written to the top) is that the condition of the if clause is true
if ($Sender->Data('Answers'))
. So what if there would be no Answers? Nothing would be written - that's the key! We either have to prevent that this info is stored or we have to delete it. Here is the part of the plugin where the information is set:Prevent the data from being fetched from the db?
Generally spoken we can only change anything if we see a
fireEvent('...');
. In thegetWhere()
method of the CommentModel would be such a fireEvent(), I guess, but this is more complex that it should be. We wouldn't be able to tell if we take influence on exactly that call or on any other plugin/class calling this method, so no need to look at the CommentModel. We simply let the assignment happen."Unset" $Sender->Data('Answers')
The code snippet above is executed when the event "BeforeDiscussionRender" is fired. The answers are written when the event "AfterDiscussion" if fired. All we have to do is to find an event that is fired after BeforeDiscussionRender and before AfterDiscussion. The easiest way is to search for where the AfterDiscussion event is fired, scroll upwards and there you'll find
$this->fireEvent('AfterPageTitle');
.Try the following snippet:
YEAH!
If you wrap the setup() and the snippet right above in a custom plugin, you have achieved to get the answers from off the topic to ther original position without touching any file "which doesn't belong to you". That's great! As I've said before: preventing text from being transferred to the client is into my opion the best way but using CSS or even JS is sometimes the fastest solution and in some edge cases it might be the only solution. But not this time
This does not answer your question concerning that button, but whenever I read "I've changed the file XY and everything is great" I feel like I have to take action in order to prevent people from hacking Vanilla instead of extending Vanilla.
But let me take a look at your button question and I'll try to answer that, too.
Let's take a look at that code and I'll rewrite it a little bit in order to be able to add comments:
The result of an sql query is an object, defined by the class DataSet. And when you look at class.dataset.php, you'll find the method
firstRow()
, which ends withreturn $Result[0];
. If you know a little bit of PHP, you know thatreturn $Result[0];
means that the first element in that array (i.e. the first row in the result of an sql query) is returned. It will always be the first row - hence the nameLet's take a look at your code:
public function DiscussionController_BeforeDiscussionOptions_Handler($Sender) {
You've used BeforeDiscussionOptions, but take a look where that event is fired (/applications/vanilla/views/discussion/index.php):
Since Vanilla uses very descriptive function names, you can see that you have placed your link only in the near of the options, but the bookmark link comes first. Was that what you wanted or did you want to make this one of the options?
Please don't leaveout the curly braces.
This should check if the current discussion has "Accepted" in the column "QnA", but I must admit I do not really understand that comparison. The discussion is passed to your function as a "EventArgument" - that's how Vanilla calls it. So you should access that column as
$Sender->EventArguments['Discussion']->SomeColumnName
. You have chosen to do a case insensitive comparison, but there shouldn't be a lower case "accepted" in that column ever, so I would write that part like this (including comment):You have alogical error here: there can be more than one accepted answer and you will always only get the first one. I think this might be a show stopper for your approach, but you have to think on that by yourself.
But what you do here shouldn't be needed. Do you remember my first answer? I've solved the dsiplay "problem" by setting $Sender->data('Answers') to null. So obviously all Answers are already available. You do a database lookup. Avoid that whenever that is possible, since it slows down your side.
You could theoretically replace that by
The only problem is that in my post above, I have told you to delete that value and so it wouldn't be available any more, since "AfterPageTitle" (the event we used to unset the value) is called before this event.
So forget the complete method where the value is unset and we do this here in this method. It is early enough.
Add
$Sender->setData('Answers', null);
to this method, since a) we already stored the Answers in the local var $Answers and b) do not want to see them later on right below the discussion.Sorry for that extra complexity...
Basically you want to write `Skip to Answer.
I'm lazy. Most of the time you can take a look at how others solved your problem. There are many links to comments and you could simply take a look at how the has been made. On a normal "/discussion/123/blablabla" page, every comment has a link if you look at the html, they all have the css class "Permalink". Do a full text search in /applications/vanilla/views/discussion/ for "Permalink" and you find a dozen results. One looks very promising:
$Permalink = val('Url', $Comment, '/discussion/comment/'.$Comment->CommentID.'/#Comment_'.$Comment->CommentID);
Although you might not be able to understand everything, it should be obvious that this builds something with "/discussion/comment", "CommentID" and "#Comment". So simply copy this!
Although this would be working, sometimes it doesn't hurt to think twice. If there is an url provided, why give an alternative. Maybe the column url is always filled. Take a look at the table Comment in the database...
Surprise, surprise: there is no such column! So no need for "take a look at comment for url, but use thisandthat and if you cannot find the url".
Let's cut it down to:
There are some "new" things in here. Instead of using html and the anchor tag, I used Vanillas
anchor()
helper function. It will ensure that your link would look right even on forums which are in a subfolder like that: www.example.com/forum/discussion/123/blablabla. It's handy. Look it up in /library/core/functions.render.phpI wrote
t('Skip to Answer')
and not only the string. Everything you enclose int()
can be translated. Just get used to do it that way so that your plugins will be usable even for non-english speakers.I've taken a look how the $Permalink var is processed further on below where I found the code snippet and it got that rel=nofollow atribute. So I simply copied that without further thinking. I'm lazy.
In one piece:
@R_J
Thanks for a such a detailed response, learned a lot from it.
I agree that extending is always better than hacking, but when you don't know where to start you go with what you can do.
There were a few typos in your final code, namely
$Answer
, instead of$answer
.Also, in my tests this didn't work:
The other solution i used, with $sender->data, that i copied from the QnA plugin, worked.
I have a few other queries if you got the time:
This code, from my example above works when its on the QnA plugin, but doesn't outside of it, don't know why:
Could you explain a bit more of how anchor() works, is the ,'', after the href really needed?
I took it out and added a class before rel in the next array, nothing went wrong.
Some plugins use $sender->Data, others $sender->EventArguments, other use a $args in the function, along with $args['something']->more.
I get confused by all of that.
From your code and deduction it seems to me that "Data" is part of the runtime object, hence why you set it in one script(QnA) and called it on another(this script), not sure what the difference between the other 2 is, or how, when to use them.
Thanks to you I learned something awesome yesterday.
BeforeDiscussionOptions works, but i figured: what if i wanted AfterDiscussionOptions?
I learned that if i edit
/applications/vanilla/views/discussion/index.php
and add this:$this->fireEvent('AfterDiscussionOptions');
afterWriteAdminCheck();
it just works. Awesome!Is there a way to do this as an extension, vs a 'hack' as you called it?
In that same vein, of avoiding editing the original files, what if I want to edit something that is a text, like the 'Most recent by' in the post listing, and make it a link to
discussion#latest
?I don't really like how the posts always go to the latest discussion, but I don' want that option to go away.
It's not obvious to me if this type of "extension" is even possible, but it may come in handy.
I know the original text can be hacked of course.
Finally, The 'FirstRow()' worked because I thought only one answer would be fine. Now I'm thinking it should cycle between the answers if there is a 'next' one.
Can you give any suggestion on how to do that?
My way would be to relay the commentids to javascript and let it check on the address if it is at comment id, if so switch the link with the second id and cycle through. But maybe you have a better way, in php fashion that will integrate well with the plugin.
Thanks again for all the great responses, the button is almost done and i learned a few things.
Well, I wrote that code without testing. Another thing is that I try to use new coding conventions whenever possible and the result is inconsistent code. But take it as a challenge to review my code.
I will not answer your questions in the order you've asked them, but I'll try to answer all of them.
anchor() first and EventArguments, args, data next, since those two questions/answers are basics for getting a better understanding of Vanilla.
Whenever you are not sure about what a Vanilla function does, try to find where that function is defined in the source. One of the most useful tools for writing plugins for Vanilla is an editor with full text search capabilities. Search for "function anchor(" across Vanilla to find useful information:
In
/library/core/functions.render.php
you'll find the anchor function:And that might already be the answer to your question
anchor accepts five arguments. I "skipped" the $CssClass argument since I didn't want to give that link a special css class, but I needed to add $Attributes. I think it becomes obvious to you if you think longer about that: if you want to "use" an argument in a function, all previous arguments must be provided before. Let me put somewhat naive: there is no way for PHP to tell if an argument should be the $Text, the $Destination, the $CssClass etc, than the order in which you provide the values.
The way the anchor function handles its arguments makes it somewhat hard to experiment by simply leaving out the class argument. Just by looking at the code I would think that any array provided as the third argument would be treated the same as $Arguments, which is merely a coincidence. It is better to use a functions the way it is documented and not the way it could be "misused". Since anchor is a bad example for arguments, please pick another function if you still have questions on their arguments :-/
Next answer will take some time...
And I'm afraid to say that you need to get a basic understaning of objects/classes and MVC in order to fully understand it. Let me start with how events work in Vanilla.
Each file you find in /applications/vanilla/controllers + models contains one class. The views are no classes but "simply" markup and as least as possible code to generate markup.
ModelViewController in Vanilla is quite easy.
The Controller
If you open the page "/discussions/mine", the code that will be run could be found in the file class.discussionscontroller.php in the controllers folder. Just open that file and you will find that the class defines a method "mine". If you simply open "/discussions", Vanilla would use the method "index". It then fetches the needed information by using the model and displays it by using a view.
The Model
In order to show "mine discussions", there needs to be made a query to the database. DB queries are made from within a model. If you search for a class.discussionsmodel.php in the models folder you will find that finding the "correct" model or even the used model method is not as easy. You most probably would need to take a look at the method in the controller. But anyhow: if the controller needs to display data from the database it will fetch them by using a model.
The View
In nearly all cases when you look at a "yourforum.com/controller/method" scheme page you will find a /views/controller/method.php file in either /applications/conversations|dashboard|vanilla. Just a hint: all user related files should be in dashboard, all forum related (categories, discussions, comments) in vanilla.
Those views consist mostly of markup, but sometimes there need to be more logic for building the markup. In such cases Vanilla tend to use a file with the name "helper_functions.php" that resides in the views subfolder.
Now let's proceed to Vanillas events
fireEvent() and EventArguments[]
In any of those three files there might appear lines like
$this->fireEvent('Whatever');
. Most of them have one or more lines like$this->EventArguments['key'] = 'value';
before them.In order to use such an event, you need to look at the class name (which is equal to the file name). If you find a fireEvent in the DiscussionModel (e.g. "BeforeGet"), you can take influence on what is happening by adding a method
public function discussionModel_beforeGet_handler($sender) {...
to your plugin. If the fireEvent is in a e.g. the ProfileController, this would be the first part of the method.But views do not have such a class name and that's why you have to use the name of the controller which renders the view.
What is the difference between $sender->EventArguments[] and $args[]?
Easy: there is none. There is no difference at all. If you haven't defined the $args as a parameter, you will not be able to use that variable further on while $sender->EventArguments is always possible, but that doesn't count as a real difference I'd say. Let's use /applications/vanilla/views/discussion/discussion.php as an example:
If we like to add an extra css class to every discussion, we could do it any way we like:
Our event hook is called with two variables (this is important!):
$sender, which is the objet that fired the event (DiscussionController in our example)
$args, which is the EventArguments array
Since $args is the same as $sender->EventArguments, you can choose whatever you like. I tend to use $args since it is shorter.
What about $sender->data()?
Now it's getting really hard. I start with an extract of the discussion.php file from above, this time with some lines less.
What is $this->data() and $this->EventArguments? $this is the DiscussionController and data() is a method of that controller, that's what could be told by looking at
$this->data()
. So please open the file class.discussionscontroller.php and try to find the method data...Nope you are not blind, it is not there. But look at the class definition:
class DiscussionController extends VanillaController
. That implicits that DiscussionController has all the methods that are defined in VanillaController and so you have to search for class.vanillacontroller.php and search for the data() method. Nothing here, but:class VanillaController extends Gdn_Controller
. Now there is no class.gdn_controller.php. Whenever you find a class name Gdn_... in Vanilla, it is most probably part of the core (Vanilla uses its own framework which is called Garden). Open class.controller.php and finally we found a method called data()!If you have no idea what that means, search for "function valr" and you'll find that documentation: "Return the value from an associative array or an object. This function differs from GetValue() in that $Key can be a string consisting of dot notation that will be used to recursively traverse the collection.". It is defined like that
function valr($key, $collection, $default = false) {
So it searches for path in a variable called $this->Data. "What is $this->Data?" you ask and that is a good and important question. It is a variable defined in the class.controller.php, see at the top of the file.
So this array is like a storage for anything that a controller uses. It is most often used as temp storage when a controller renders a view. The controller stuffs information in this array and the view extracts it. This is their way to transport information. It is not done by using the array as you normally use an array (
$this->Data[$key] = $value;
), but with two functions:Controller:
$this->setData('Title', 'Awesome View Title');
View:
<h1><?php echo $this->data('Title'); ?></h1>
Remeber where you've used that function?
$Sender->Data('Discussion.DiscussionID')
. So with the knowledge of the controllers Data[] array and the data() method, we can now tell what this code does: it tries to find Data['Discussion]['DiscussionID']/Data['Discussion]->DiscussionID where Data[] is "this controllers internal storage array".Back to our event hook example:
discussionController_beforeDiscussionDisplay_handler($sender, $args)
.Now that we have seen the code in the view:
we can tell that the discussion is stored in the Data[] array of the controller. So all of this would be exactly the same:
$sender->EventArguments['Discussion']->Name
$args['Discussion']->Name
$this->data('Discussion')->Name
and even
$this->Data['Discussion']->Name
Final thoughts
But if they are all the same, what should be used best? Is it really all the same? Nope, definetely not.
Never access $this->Data array directly. There is setData() and data() to set and retrieve those values. If Vanilla Dev Team one day decide to change the way a controller can store and interchange data, that array might be abolished and those two helper functions will simply be tweaked to use the new storage method.
Although I think this is very unlikely, there really is no reason to access this array directly, so don't do so.
Only use $this->data('whatever') in a plugin if you have no other way to get access to that information. The reason is simmilar to the reason why you should not use the Data array: what is accessible with the data function might change. This is only for internal use and that's why the availability of this information is not guaranteed. Your plugin might be broken after an upgrade because some information is no longer available or - and that is much worse you expose sensitive data because the content of the data has changed.
If you use it be extra careful and be prepared that this might need to be worked on after an upgrade.
The EventArguments array is explicetly filled with data that is important for plugin developers. So it is recommended to use it. How you access it makes no difference at all and is completely up to you.
Although you can use $sender->EventArguments, I'd recommend using $args mainly because it is more readable.
This not of importance, but I prefer $args for another reason, too: $sender->EventArgument accesses an array of the calling class. If at anytime the way event data is passed to a hook will change, the data will surely still be send to the event hook as an argument. Anytime you use a functionality that Vanilla provides, you will be prepared even for bigger design changes of the underlying framework. But believve me: I consider myself being a nerd for thinking that way: there is not even a single sign of such serious changes!
I'm sure there has been lots of stuff you already new, but since I don't know what your skills are I prefer explaining some more. Please don't feel disrespected or anything like that.
Altogether this is some heavy stuff and if you have understand it, you might be able to answer that question by yourself:
But there are other reasons why this might have failed. Without seeing the context I wouldn't be able to answer that
Although that makes me shiver, I have to congratulate you for taking the least invasive way to change core files! If you really have no way to do what you like without changing a core file, inserting a fireEvent is the best way to do so.
By the way: if you think that event can be useful for more developers, you could make a proposal for that. Either you make a pull request on GitHub or open an issue.
In order to answer your question in a way that you take the most benefit from it I tell what I'm looking at when thinking about such a problem. But I think there are some ugly ways to achieve something like that but no elegant way.
You need to insert something before the last div is what I understand that you try to achieve.
Dig deeper
Since you want to put something after
WriteAdminCheck();
, I take a look at that function. Maybe this function also fires an event and we can use that?Nope, no event here.
Suppress output between two events
This one is not intuitive and doesn't work often. Look at the code from the event before you want to change something up to the next event. What if we would be able to make everything that is echoed between the upper fireEvent and the lower fireEvent echo nothing? If the output is only conditional that works from time to time.
Simply by looking at the above function we could see that if we change the values of the variable and the config setting, we would be able to suppress the output. If this would be possible for all output, we could use event 1 to set the variables in a way that there will be no output and then in event 2 change the variables again and output everything in the order we like. Somewhat tricky, but when two events are fired and there is not much happening between them, chances are high that you have success.
But we have no luck here:
Whatever we do in BeforeDiscussionOptions, all those echo lines will be written to screen.
Forcefully suppress output between two events! (fucking ugly and I don't know if it really works)
Really no way to suppress them from being written to screen? Well, no. They will be echoed, yes but do you ever have heard of output buffering? Search php.net for ob_start.
This is a little example
So what if we start output buffering in event 1 and in event 2 delete what has been written to output? Then whatever happend between both events is not on screen. great. Afterwards you would have to simulate that output. Here is some untested code
One risk is that the output already gets buffered and that's why before you start buffering in event 1, you would have to get what is in the buffer by now, store it temporarily and after you have cleaned the buffer in 2, you would have to use ob_start again right after ob_end_clean and echo what you have temporarily stored.
But keep in mind that everything that happens between those two events must be done twice, which takes time. This is really, really ugly. But I thought I tell you even about the ugly ways to achieve something like that.
"Override" a function
Back to
WriteAdminCheck();
. What if we could change it so that it not only does what is meant for, but also write our custom code? That would be handy. And we would only change one not very complex function. The function definition has that line on topif (!function_exists('WriteAdminCheck')):
so if we definethat function in our plugin, our custom function would be used!If you add this below your plugins class you have "overridden" the function:
Well, looks really easy, but what if this function is used somewhere else? Our markup would appear there, too. This might not be desired. So we should prevent it to appear anywhere else. Do you still have the file class.controller.php opened? There are some class variables that will be really helpful! See through them if you find one that looks promising to you, or simply read on
That should do the trick.
Replace the view
Sometimes you'll be able to make a Vanilla controller use your view. Copy the index.php into /plugins/yourplugin/views/discussionindex.php add your new fireEvent line in your copy. Although we now have our own copy we should change only the minimum.
Then look at the DiscussionControllers index method. The last command in there is
$this->render();
this causes Vanilla to render the view that previously has been set. If no view has been set, it will try to find a view with the same name as the method in the views subfolder for this controller.If we like to trick Vanilla to use our altered view, we need to set the view manually. Look for the last fireEvent in the index method of the DiscussionController. We want to set our own view right at the end so if a view that has been set earlier will be replaced.
For a better understanding, take at the class variables of the class.controller.php again:
Its a public variable, so we can set it easily.
Ridiculously easy, isn't it?
$this->getView()
might need some additional explanation. $this is the current class = our plugin. Our plugin has some methods more than we declare in our plugin because it extends the Garden plugin class. getView() is a method which looks for the given filename in the plugins "views" subfolder. It returns the files path (which is required in the controllers $View variable)JavaScript
Not my favorite, but it is a possibility. Output your markup at the end of the screen or in the near of where you would like to see itand change the DOM with JS as soon as the page has finished loading.
You have to ponder if the above shown possibilities are too dangerous/too invasive and/or if JS might be a good alternative. It's up to you.
And the winner is...
I don't know. I would advice you to either override the function, the view or use JavaScript.
JS is least invasive, but I personally do not like rendering to screen and changing it afterwards. Feels wrong.
Overriding complete views bears the danger that after an upgrade your plugin prevent users from seeing new features or even showing them a messed up screen.
Overriding a function feels not good either. What if anyone else had the same idea? If two plugins override the same function it couldn't work.
I'm happy that this is your decision and I only had to show you the possibilities
I forgot something in the above:
CSS
Add your markup where you can add it and hope that you are able to use CSS to push it where you like it to be. That would be the best way, without a doubt.
And I forgot it...
The next one is easy!
No, it is not possible. You could do it by overriding the view, but this time we are lucky:
There is nothing between what you like to change and the fireEvent. So simply add he code you would like to see and hide with CSS what you do not like.
I haven't understood that "cycle" idea. You search for a way to make all comments accessible right from the discussion, correct?
I think it would be handy if all answers (as well as the question) are accessible from the question and from every answer.
A module might be a solution (modules are the boxes in the sidebar)
Or something like
or
below the question and each answer (or even each comment?)
But I'm not very creative when it comes to UI questions...
@R_J
I found the problem with the sql request I mentioned, it was a typo. I opened it up in the right editor and spotted it right away. ($Sender vs. $sender).
I got crazy with all the things you taught and was able to modify everything without having to edit core files.
I'm linking the plugin that makes the edits i wanted(LatestComments).
It clears all the meta info in the discussions list, and only displays what is relevant when it's relevant. Furthermore, now posts link to their 1st post, but you can click the 'Most recent by', if you wanna see the latest.
Had to use lots of those use the event after or before ideas there. It's a handy plugin if anyone wants it.
The Answer Buttons is a work in progress, but the basic functionality I wanted is there.
About the js i think i'm going to do a floating button.
After a certain % of the screen it floats to the side in a fixed position and becomes a button that hovers with you. But that's all js doing it's thing(I'll likely use jquery), so no mystery there.
I'll post the feedback if i get it done or get involved in any hiccups.
Thanks so much.