Earlier this morning I uploaded a beta version of NAMFox that builds upon what I've already delivered in the 1.6.3 release. I'm trying out something new here by releasing beta versions every week (as long as there is some new feature or bug fix available) to see if it helps encourage you guys to give feedback on new features so I can make them as good as possible before releasing to the public.

Of course, any beta release is optional to download. But if you do download a beta, then Firefox will always let you know when there are beta updates available every week. You can provide feedback in the same way you can today--posting threads in the appropriate NAMFox forum to report bugs or make suggestions.

The first beta release is out now: https://addons.mozilla.org/en-US/firefox/addon/3995/versions/?page=1#version-1.6.3.75beta You can read the release notes at the link above. Enjoy!

web development technology neoseeker related namfox namfox beta

Read more

I've uploaded NAMFox 1.6.3 to the Mozilla Add-Ons site (not yet reviewed). Again, there are only a few bug fixes, including resolving the issues around Quick Edit that recent changes in Neoseeker caused. New features should be coming in the next version.

Also, this version supports Firefox 4.0 Beta 1. I know Beta 2 is now out, and if you want to disable Firefox's compatibility check and try it that's fine. However, I believe there are breaking changes which will cause it not to work. I'll keep working on this over the next couple of days.


Bug Fixes

  1. Quick edit doesn't interpret sup and sub tags correctly
  2. Memory leak in AutoComplete




Thanks,
Art

web development technology neoseeker related namfox namfox release

Read more

I've uploaded NAMFox 1.6.2 to the Mozilla Add-Ons site. There's just a few bug fixes in this one—nothing too fancy.


Bug Fixes

  1. Quick reply double posts
  2. Signature disappears after quick edit
  3. Slow performance using Neoseeker when using NAMFox and your account name has a space in it.



Note that if your user name does have a space in it I would recommend you download this new version if you are currently using NAMFox today. Previous versions of NAMFox would cause a heavy load on the Neoseeker servers for those of you with spaces in your name, so you may have experienced some slowness or hiccups with your Internet connection. If that's the case you should see things speed up a bit with this new release.

Thanks,
Art

namfox namfox release web development technology neoseeker related

Read more

With Christmas fast approaching it's time to create some of the classic Christmas dishes, including today's special—cranberry sauce.

Recipe

Ingredients (US Units)

  • 6 whole cloves
  • 1 cinnamon stick
  • 3/4 cup sugar
  • 1/2 cup water
  • 2 tablespoons balsamic vinegar
  • 12 oz. cranberries

Steps

1. Wrap all of the cloves and the cinnamon stick in a cheesecloth. This was my first exposure to "cheesecloth"—we'll cook the cranberries with the spices in the cloth so they don't get lost in the sauce. Nothing worse than eating something only to discover you've taken a big bite of a cinnamon stick. :)
2. Next, combine the sugar, water, vinegar and spices in a saucepan and put on high heat until the mixture starts to boil; then bring down the mixture to medium heat and wait for the sugar to dissolve completely. This will take about 5 minutes.


3. Then pour the cranberries into the saucepan and cook them under medium-low heat until the cranberries burst and the mixture thickens, about 15 to 20 minutes. Then remove the spice bag from the mixture.
I was really surprised how easy this recipe was. We spent more time looking for ingredients than actually preparing the dish. I haven't had a chance to try it yet since it's not Christmas, but I'll update this blog when I have! :)



other recipes food cooking sauces

Read more

Most of what I know about cooking I learned by watching others prepare food and talking to them about their techniques. The problem with doing this is that when you want to recreate their dishes, you have to stitch together your memories of what you saw and what they said and pray that you end up with something that looks (and tastes!) halfway decent. So, in an endeavor to solve this problem, I've decided to write a blog post about each recipe that I see so I no longer have to keep these tucked away in my memory. As an added bonus, I can share the recipes with you guys and get some feedback about things I can do differently in future. So let's get started!



The first dish that my mom and I prepared tonight was rainbow trout. It was a pretty straightforward dish, but sometimes it can be a little intimidating to look at fish and think "what the hell do I do with this?".

Recipe

Ingredients

2 rainbow trout, organs removed (ours were about a foot long)
Olive oil
Small amounts of butter (1 oz. maximum)
1 shallot
White wine
Salt and pepper

Steps

1. The first thing to prep the fish is to scale it if the fish you buy still has its scales. To do this you can take a knife and start scraping from the caudal fin (i.e. the tail) and make your way to the head. Keeping the knife at a 45 degree angle (being careful not to slice into the fish) also helps scale the fish more quickly. We did this in the sink, so it was easy to keep water running to help remove all the scales that build up on the knife.
2. Afterwards, you'll need to wash the inside and outside of the trout (shown below). While you're working on the trout, add some olive oil and butter to a pan under low heat and preheat the oven to 400 degrees.
3. You'll now have a wet fish :), so take a paper towel and dry the fish as much as you can.
Now we turned the stove up to medium heat and seared the trout in the pan. Although we had good intentions (reduce the overall cooking time), we shouldn't have done this because it ended up ripping the skin from the fish (see below). We cut our losses and moved the fish to a casserole dish before seasoning it with a bit of salt and pepper.


3. With the fish set aside we chopped up a shallot and threw it in the pan and waited for it to caramelize. I like shallots more than onions because the flavor they produce is not as intense as onions', so it doesn't overpower the main course.
4. When the shallots are done, we poured the remaining mixture from the pan over the trout.
5. The last step was to flesh out the sauce with some wine. We used a small amount of white wine and let that simmer for a bit before pouring that on the trout in the casserole dish.
We stuck it in the oven for 20 minutes and then had a delicious meal with it. I enjoyed the flavors, but they were a little plain, and the fish still had tons of bones in it. The presentation also could be a little better next time (don't rip the skin off).



There were actually more trout in the container than what we cooked tonight, so went through the first couple of steps of scaling, cleaning, and drying the fish before wrapping them in Saran wrap and aluminum foil to freeze. Then I suspect it will be simple enough to thaw them and try this again, perhaps with some different seasoning this time. Bon appetit!

other food food recipes recipes fish fish

Read more

Hey guys, I wanted to let you all know that after a long and arduous journey, NAMFox has been approved as a public add-on to host on addons.mozilla.org! This is a big step in improving the visibility of NAMFox as well as the availability of the host. I hope this means there will be no more issues downloading NAMFox. :)

If you have NAMFox 1.6, you should be prompted to download 1.6.0.1 from the addons.mozilla.org site. There are no functionality changes in this version, just a couple of changes to make NAMFox play more nicely with other add-ons.

To all of you who took the time to write reviews for NAMFox, I really appreciate your help. I couldn't have gotten NAMFox approved without your help.


NAMFox at AMO


web development technology neoseeker related namfox firefox

Read more

Our business in life is not to get ahead of others but to get ahead of ourselves—to break our own records, to outstrip our yesterdays by our today, to do our work with more force than ever before.

Stewart B. Johnson



A couple weeks ago I realized my efforts in "outdoing myself" were slack, not because of a lack of ideas, but because of a lack of time. There are so many things I know I can achieve, but they stay on the backburner until I have time to pursue them.

It wasn't until last week that I had the idea to take a sabbatical from Neoseeker to start attacking this long backlog of self-improvement efforts. As a result, I began talking with Redemption, a few supermoderators and my good friend bobbonew to see if we could come to sort of arrangement. Fortunately, we did. I recently released a new version of NAMFox alongside the new forum upgrades, both of which help facilitate the all-desired self-management of the bug forums that Redemption and I discussed only a couple months ago. The supermoderator team was very accommodating with my request; I especially appreciate Solitaire's willingness to talk through logistics with me. Finally, bobbonew has volunteered to moderate the bug forums and Web Coding while I am away, and for that I am very grateful.

So if you have any issues with those forums over the next few months, please talk with bobbonew instead of me. If, however, you encounter NAMFox issues that require immediate attention, shoot me an email, and I'll be sure to see it.

Thanks for your support! I'll see you in a couple months.

other sabbatical

Read more

Throughout middle school and high school I had four years of Spanish classes, and while I thought we were learning a lot of material, I remember now that we were not truly engaged in wanting to learn. I recently realized that I didn't want those years to go to waste, though, and so I started learning Spanish on my own. The best way that I found so far (within my budget) was to use the tools available at SpanishPod101.com.

If you haven't been to SpanishPod101.com and you're interested in learning Spanish, then I recommend that you take a look. Their premise is simple. Don't pay a cent and you get access to free podcasts, each of which usually involve some Spanish monologue or dialogue along with an audio translation. Pay a little bit to gain access to PDF transcripts of the lessons so you can examine grammar and spelling in more depth. Pay a lot more to access all of that in addition to online learning tools—vocabulary flash cards, lesson reviews, pronunciation practice—the list goes on.

In the beginning, I opted for the "pay a little bit" plan. Free is nice but not being able to synthesize audio with its written equivalent was frustrating for me as a language learner. Plus, without any concrete references I would need to listen to the entire lesson again just to recall the key points, which is not worth the time. In order to remember the words from the lesson I would take the vocabulary list in the PDF transcripts and add them to Anki, an excellent piece of flash card software that schedules cards you miss more often than those you get right. Check out the videos if you are interested in Anki.

This worked well over the course of several months, but I soon became tired of the process. In order to learn successfully, I had to spend a lot of time on "prep work": (1) downloading the podcasts and PDFs from their web site, (2) loading each podcast into iTunes, (3) opening the PDF in Adobe Reader, and (4) entering the vocabulary into Anki. That's a lot of context switching for my purposes, so I set out to change all that.

So in between NAMFox releases, I put a tool together that gets me most of the way there.



It's pretty minimalistic, with only a few buttons and knobs to mess around with. Clicking the green arrow in the top left takes me to the download manager, pictured below.



The *pod101.com web sites are blogs which each have multiple pages. On each page there are on average five posts, each of which contains some content to download. Each blog post has a section like this where you can download various resource material:



The major pain point I had is that it is painstakingly time-consuming to download each set of materials into their own folder; even with a fast computer, specifying save locations for every set of files is boring and error-prone. Consequently, I built the download manager to take care of it all for me: visiting the web pages, downloading the content, and saving it into my preferred folder location.





The next step is to take what we've downloaded and learn! As I mentioned before, I would normally use a combination of iTunes for the audio/video and Adobe reader for the PDF, but now I've found a much more viable alternative...



Choose a lesson and then start listening to the audio or watching the video along with reading the PDF:



(The image is a little squashed to fit the size constraints here—it looks much better maximized. =) )

This a good start for me, and I hope the effort pays off. The next feature I want to work on will take the vocabulary I find in the PDF and make flash cards in Anki automatically, but that will take some time to do correctly. Oh, and one more thing—if I ever have the urge to learn French, I can just turn the application to start looking at FrenchPod101.com instead. :)



Anyway, I just wanted to share with you guys what I've been working on. Cheers!

language technology

Read more

I am strongly of the opinion that most bug report forums on Neoseeker don't need moderators. That might seem strange coming from someone who's moderated the Forum and Site Bug Reports forums for a year and a half, but I have learned a lot of things about the forums that suggest they can be run differently.

I believe that the typical moderating experience for a bug forum is very poor. The moderator has the onus of updating statuses for each and every thread that comes into the forum; this might be easier if the pre-established statuses were plain text, but as far as I can remember bug statuses have always involved fancy flags (, , , , , ...). As a result, moderators would have to remember statuses and their corresponding flag colors in order to set a status:
  • Status: Confirmed
  • Status: Resolved
  • Status: Unconfirmed
And yes, the status list does go on.

Another problem with moderating bug forums is that they really do require some technical savoir faire for some situations, especially bugs that concern security. For example, bugs that report security holes should be taken out of view to avoid being exploited by malicious users. Sometimes, these sorts of bugs are not obvious to the innocent bystander. Take the use of JavaScript in NeoHomes in the pre-2007 days—while it enables some really neat functionality, it is a gaping security hole that someone could take advantage to hack their way into someone else's account.


Double clicking any of the options shown changes the caption.
Being a technologist, I love to streamline processes where I can with software, and this was no exception. Part of the motivation behind NAMFox's Quick Caption feature was to greatly decrease the time necessary for moderators of caption-heavy forums to set and change thread captions. For those of you who are not familiar with the feature, it allows moderators to set captions directly from the forum page itself, not a thread management page. This way moderators can set captions for multiple threads all from the same page. Moderators with pre-defined captions (read: bug forum moderators) benefited even more because Quick Caption allows them to choose from a default set of captions.


Still, it could be better. One thing I noticed particularly in the Forum and Site Bug Reports forums was that once a bug was fixed, the bug report was deleted. This struck me as a little weird—what if a bug "comes alive" again? Wouldn't you want to know all the information that you painstakingly gathered the last time it happened? The reason for the deletion was that closed bugs cluttered the regular bug report forums; thus the Archived Forum Bug Reports and Archived Site Bug Reports forums were born. All closed bugs now end up there, waiting to be re-activated...
    Aside: I think these archived forums would be more useful if there were a way to search for bugs across both the original forum and the archived forum, but maybe that is something in the works. :)
Those forums have existed for about a year, and we (Redemption and I) haven't done much work on the forums since then.

At least, until I started becoming lazy again.

It became apparent that as soon as new bug reports were created they each entered a workflow where the system (e.g. Neoseeker itself) could handle a lot of manual work moderators previously did.

In this latest Neoseeker update you can see a couple of new features in the bug forums that help realize this workflow:
  1. Users can now mark bugs as confirmed. Previously moderators would have to look through threads for people's posts to say "Confirmed" and then mark the confirmed status themselves.
  2. Bug status is now an integrated part of the Neoseeker system. Moderators who have jurisdiction in the bug forums can set the status to one of a set of pre-defined bug statuses, and certain statuses will trigger certain actions. For example, setting "Bad Bugged" will auto-delete the thread in seven days, and setting "Resolved" will PM the thread creator to confirm the bug was fixed to their satisfaction.
This is only the first step, and we have a lot more to do. Ideas include automatically moving threads to the appropriate archived forum when the thread is closed, allowing members to re-activate bugs in the archived forums, and allowing thread creators to close bugs once they are resolved.

Then the last step to completely eradicate moderators would be to transition the responsibility of moderators to the Neo-staff themselves. There are some cases where bugs have been fixed but no one posted in the thread, so the thread stays open as an active bug. In actuality, staff can actually change the statuses better than moderators can, since they have intimate knowledge of the system itself. Looking at a very simple workflow reveals that there really isn't a good place for moderators:
  1. Users report bugs.
  2. Other users confirm bugs.
  3. Staff resolves the bugs.
  4. The original user then confirms the bug is fixed.
The moderator doesn't facilitate this ideal workflow.

I admit there are various administrative tasks that moderates can facilitate, such as marking bugs as duplicates. However, I personally believe that the product team themselves should be aware of the bugs that are being reported (especially those that are reported more frequently!) in order to gain a sense for prioritizing bug fixes. In my opinion, the moderator feels like an awkward middle man that actually stifles communication between bug reporters and bug fixers. Of course, I don't know the intimate inner workings of Neoseeker, so in this case having the moderator take care of these duties might outweigh the time cost staff would incur to review these bugs.

Of course, if you look at the bug forums now it seems like we are going in the wrong direction since bobbonew is also moderating them, but that's the topic of another post. :)

What do you think? Do you think there's a place for moderators in these forums?

web development technology neoseeker related neoseeker neoseeker forums bugs namfox

Read more






NAMFox 1.5.2 has a few bug fixes and adds supports for Firefox 3.5. I realize I am a bit delayed with this and I apologize for that. I was working on incorporating a few bits of feedback that the editors over at https://addons.mozilla.org had for NAMFox. This means we're getting close to having NAMFox accepted as a full-fledged add-on on https://addons.mozilla.org.

So for all of you who submitted reviews for NAMFox, I really appreciate it!

After this I have another side project that I need to finish, but I will be able to devote more time to bug fixes and suggestions afterwards. Enjoy!

Art






web development technology neoseeker related namfox namfox release

Read more

Forgot I had this picture of me in the Cadillac Ranch just outside Amarillo, TX. You're encouraged to spray paint. :D



namfox cadillac ranch other






After what seems like forever, I've advanced NAMFox to a point where I am happy with the latest release. There are a lot of new features, a lot of bugs that were fixed, and overall, (I hope!) a much richer Neoseeker experience.

If you have any questions about new features, be sure to check out the new NAMFox 1.5 FAQ.

You may also notice a button in the NAMFox forum header for donations. NAMFox will always remain free, but it is something that takes a lot of care and time to get right. Including all the new features, bug fixes, rewrites, blogs, and troubleshooting, this release has taken about 400 hours of work. If you really find that NAMFox helps you, I'd ask you to consider donating. But, no pressure. :P

Cheers,
Artificer

To Update:




  • Q: How do I update NAMFox?
      NAMFox is compatible with Firefox's automatic updates feature for add-ons. Usually, Firefox will automatically alert you when a new version of NAMFox is available. However, if you want to force updates, go to the "Tools" menu and click on "Add-Ons." Then click on the "Find Updates" button in the bottom left of the window.



    New Features

    1. Interface and Speed Improvements for Quick Reply and Quick Edit
    2. Reliability Improvements for Quick Edit and Quick Quote
    3. Increase Number of Custom Messages to 25
    4. Make NAMFox Toolbar Customizable
    5. Strip Colors From Posts
    6. Choose What Smileys Appear in NAMFox Toolbar
    7. Custom Smiley Should Appear in NAMFox Toolbar
    8. More Links in NAMFox Toolbar
    9. Put Default Message in Blog Posts and Comments
    10. Search for Books in NAMFox Toolbar
    11. Link Name Markup Button Should Act Like Neoseeker's
    12. Support for new Skully8 Smiley in Quick Edit and NAMFox Toolbar
    13. Support for new PlayStation Smileys in Quick Edit and NAMFox Toolbar
    14. Support New Wiki Tags in Quick Edit and AutoComplete
    15. Rearrange Quick Edit Buttons
    16. Expose Custom Messages in AutoComplete
    17. Strip Colors Feature Should Ignore Your Own Posts
    18. Strip Color Feature Extended For Blog Posts and Comments
    19. Move Quick Reply Higher
    20. Reorganize NAMFox Links in Toolbar – Includes adding NeoVids and preferences links to toolbar.
    21. Restrict Stripped Colors to Those Above a Certain % Lightness
    22. Middle Click Opens Toolbar Links in New Tab

    Bugs That Were Fixed

    1. Quick Edit Fails With Multiple Different Types of Quotes
    2. Quick Edit Fails With Multiple Successive Heading nTags
    3. No Obvious Way to Close Preference Dialog on Mac OSX
    4. Quick Reply Says I'm a Guest
    5. Quick Reply Conflicts with Neoseeker Quick Reply
    6. Quick Edit Fails to Translate Multiple Spoiler Tags Correctly
    7. Cannot Quick Edit in Some Circumstances
    8. Quick Reply Doesn't Exist in Some Threads
    9. Go to Full Edit Doesn't Send Post Body
    10. Subject and Body Not Escaped in Go to Full Reply/Edit
    11. Video URL Transforms Don't Work on Paste
    12. Open Toolbar Links in New Tab Not Working
    13. Smileys Preferences Erased if Markup and Smileys Tab Not Visited
    14. Some Smileys Not Translated Back to Their Markup Equivalents in Quick Edit
    15. PHP Tags Have no Line Breaks When Using Quick Reply
    16. Non-ASCII Characters Not Saved/Loaded Correctly in Custom Messages
    17. Non-ASCII ANSI Characters Appearing as Question Marks on Quick Edit/Reply
    18. When Quick Edit Times Out, Page Is Not Refreshed





    trainer card pokemon namfox namfox release web development technology neoseeker related

  • Read more

    Note: This beta release is no longer available. You may download the final version here.




    I apologize for the delay in getting this out. The Neoseeker team made some changes last Thursday night which broke a lot of functionality of NAMFox, and I was in the middle of making changes to support Firefox 2 at the time.

    The bright side is that those of you who are on Firefox 2 can choose to use this version, because I'm sure most previous versions of NAMFox will be broken as well.

    Assuming this release fixes the current problems, this will be the last beta of this version.

    This is a beta version, which means it is an optional update. If you want this version, you will need to download it from the link on this page. Since it is a beta version, I would very much appreciate any feedback you have, positive or negative. Bug reports and suggestions for new features are always welcome. You may post these in either the main NAMFox forum, or the NAMFox for Moderators forum if the bug/suggestion relates to moderator functionality.

    Cheers,
    Artificer




    Note: This beta release is no longer available. You may download the final version here.

    web development technology neoseeker related pokemon sprite namfox namfox release neoseeker

    Read more

    I'm hoping to get NAMFox available on https://addons.mozilla.org soon. Now that I've finally got it there, it has to go through an approval process for it to be publicly available.

    One of the items that the approvers look at is the quality of the reviews it receives. If you have benefited from using NAMFox, I would appreciate if you could take a few moments to write a review for it.

    You will need an account on https://addons.mozilla.org to do so. Don't worry; they don't send spam or anything of the sort.

    Thanks!

    technology neoseeker related namfox review

    Note: This beta release is no longer available. You may download the final version here.




    A big THANK YOU for all of you who tried the first NAMFox 1.1 beta. There was a lot of excellent feedback that I was able to incorporate into this new release, and I hope those of you who were dissatisfied with the first beta will try this second one. A lot of the issues that plagued the first version have been fixed, and there are a few new features as well.

    You may notice that this is now NAMFox 1.5. I have changed it from 1.1 because I feel that there are so many new features and bug fixes that it is vastly different from version 1.0.

    This is a beta version, which means it is an optional update. If you want this version, you will need to download it from the link on this page. Since it is a beta version, I would very much appreciate any feedback you have, positive or negative. Bug reports and suggestions for new features are always welcome. You may post these in either the main NAMFox forum, or the NAMFox for Moderators forum if the bug/suggestion relates to moderator functionality.

    The following is a list of changes between NAMFox 1.1b1 and NAMFox 1.5b2. A more thorough list (though targeted more for technical customers) is available here.

    New Features

    1. Expose Custom Messages in AutoComplete
    2. Strip Colors Feature Should Ignore Your Own Posts
    3. Strip Color Feature Extended For Blog Posts and Comments
    4. Move Quick Reply Higher
    5. Reorganize NAMFox Links in Toolbar – Includes adding NeoVids and preferences links to toolbar.
    6. Restrict Stripped Colors to Those Above a Certain % Lightness
    7. Middle Click Opens Toolbar Links in New Tab

    Bugs That Were Fixed

    1. Cannot Quick Edit in Some Circumstances
    2. Quick Reply Doesn't Exist in Some Threads
    3. Go to Full Edit Doesn't Send Post Body
    4. Subject and Body Not Escaped in Go to Full Reply/Edit
    5. Video URL Transforms Don't Work on Paste
    6. Open Toolbar Links in New Tab Not Working
    7. Smileys Preferences Erased if Markup and Smileys Tab Not Visited
    8. Some Smileys Not Translated Back to Their Markup Equivalents in Quick Edit
    9. PHP Tags Have no Line Breaks When Using Quick Reply
    10. Non-ASCII Characters Not Saved/Loaded Correctly in Custom Messages
    11. Non-ASCII ANSI Characters Appearing as Question Marks on Quick Edit/Reply
    12. When Quick Edit Times Out, Page Is Not Refreshed



    I hope you all enjoy!

    Cheers,
    Artificer

    Note: This beta release is no longer available. You may download the final version here.

    namfox namfox release neoseeker emma klamsky wanzer pilot front mission web development technology neoseeker related

    Read more

    A tribute to developers everywhere.



                                   

    technology hugs developers

    Note: This beta release is no longer available. You may download the final version here.




    Tonight marks the culmination of months of work on the rewrite and further improvement of NAMFox. In this effort I've managed to fix an extremely high percentage of bugs that were reported as well as add numerous new features that are long overdue.

    As I mentioned in my previous post, the newest updates to NAMFox, including this release, will no longer support Firefox 2. I have tried to resolve problems I have had in this version of Firefox, even by communicating with the developers responsible for some of the changes between Firefox 2 and 3, but there are just too many significant changes between the two versions to account for reliably. I know there are a few of you who cannot upgrade to Firefox 3, and I apologize for that.

    This is a beta version, which means it is an optional update. If you want this version, you will need to download it from the link on this page. Since it is a beta version, I would very much appreciate any feedback you have, positive or negative. Bug reports and suggestions for new features are always welcome. You may post these in either the main NAMFox forum, or the NAMFox for Moderators forum if the bug/suggestion relates to moderator functionality.

    The following is a list of changes between NAMFox 1.0.5 and NAMFox 1.1b1. A more thorough list (though targeted more for technical customers) is available here.

    New Features

    1. Interface and Speed Improvements for Quick Reply and Quick Edit
    2. Reliability Improvements for Quick Edit and Quick Quote
    3. Increase Number of Custom Messages to 25
    4. Make NAMFox Toolbar Customizable
    5. Strip Colors From Posts
    6. Choose What Smileys Appear in NAMFox Toolbar
    7. Custom Smiley Should Appear in NAMFox Toolbar
    8. More Links in NAMFox Toolbar
    9. Put Default Message in Blog Posts and Comments
    10. Search for Books in NAMFox Toolbar
    11. Link Name Markup Button Should Act Like Neoseeker's
    12. Support for new Skully8 Smiley in Quick Edit and NAMFox Toolbar
    13. Support for new PlayStation Smileys in Quick Edit and NAMFox Toolbar
    14. Support New Wiki Tags in Quick Edit and AutoComplete
    15. Rearrange Quick Edit Buttons

    Bugs That Were Fixed

    1. Quick Edit Fails With Multiple Different Types of Quotes
    2. Quick Edit Fails With Multiple Successive Heading nTags
    3. No Obvious Way to Close Preference Dialog on Mac OSX
    4. Quick Reply Says I'm a Guest
    5. Quick Reply Conflicts with Neoseeker Quick Reply
    6. Quick Edit Fails to Translate Multiple Spoiler Tags Correctly



    And I'm spent. 280 hours of work since December 18. A lot of that work happened behind the scenes, improving the infrastructure and maintainability of the product, as well as introducing an automated means to test that various features are actually working. I'm very happy with this release and hope you all are, too.

    Special thanks goes to lord monkey for helping find bugs in the pre-beta builds, as well as Quierta for providing feedback on the new look for Quick Reply and Quick Edit.

    Cheers,
    Artificer

    Note: This beta release is no longer available. You may download the final version here.

    namfox neoseeker namfox release web development technology neoseeker related

    Read more

    After much consideration, I have decided that NAMFox 1.1 (the next planned release of NAMFox) will run ONLY on Firefox 3 and above.

    I make this choice for a couple of reasons. The first is that there are a lot of pieces that the Mozilla team have exposed in Firefox 3 that allow NAMFox to work more quickly and more reliably than ever before. Since my job as a NAMFox developer is to provide you with a rich and gratifying experience, I feel that you should be able to take advantage of these features as quickly as possible.

    The second is that Mozilla has stopped security and stability support for Firefox 2 as of December 2008 and strongly advocate that you upgrade to Firefox 3 to continue receiving this support. The same support stopped for Firefox 1.5 in May 2007.

    If you are still running an older version of Firefox, and you do not want to upgrade to Firefox 3, then you will still be able to run NAMFox 1.0.5. Although, I do strongly recommend you upgrade.


    Comments, feedback, complaints, threats? I'm especially interested in the reasons that folks don't want to upgrade to Firefox 3.

    technology neoseeker related namfox neoseeker

    Read more

    One of the awesome features (IMO) released in NAMFox v1.0 was AutoComplete, a feature for typing Neoseeker's markup even more quickly than ever before. If you haven't experienced or seen AutoComplete, take some time to check out this brief tutorial:



    You may notice that AutoComplete pops up a bit under the caret (or cursor) in the text area. It's funny in retrospect, but by far the greatest impedance to implementing this feature was that there was no way to get the (x,y) coordinates of a key press from Firefox's event model. No coordinates means no way to show AutoComplete in the correct location. Surely, if you browse to the event page you see that there are properties for accessing those coordinates (clientX, clientY, pageX, pageY, etc.). However, for key events, there is no place to hold that information.

    So this was actually a big problem! I tried asking here on Neoseeker and on the Mozilla Add-On Development forums to no avail. At the time, I couldn't think of a good alternative to finding the coordinates based on the caret position in the text area. What I eventually "implemented" was as follows:
    • Given the caret position (which by happenstance was provided) in the text area, get all of the text that appeared before it.
    • Parse this text into lines based on the current width of the text area, as well as other parameters (e.g. whether there was a scroll bar). When I say "lines", I mean the lines as they appear in any text area with text in it. For example, looking at the text area in which I'm writing this blog now, I see the lines break down like this:
      code
      * Parse this text into lines based on the current width of the text area,
      as well as other parameters (e.g. whether there was a scroll bar). When I
      say "lines", I mean the lines [b]as they appear[/b] in any text area with
      text in it. For example, looking at the text area in which I'm writing
      this blog now, I see the lines break down like this:
      


      This was extremely difficult because there were a number of different parameters to consider: the Firefox version (2.x and 3.x had different rules for when to make a new line in a text area), the zoom level, the user's choice of font for the text area (what to do if it wasn't monospace?), and so on. My solution was to hard-code character widths and heights under different zoom levels and versions of Firefox based on size 10 Courier. (Some of the Mac users may notice that with AutoComplete turned on, the text in their text areas looks strange. This is because I had to force text areas to use size 10 Courier for this to work.)
    • Multiply the number of lines by the height of each character to find how far from the top of the text area the caret is. I could use a similar approach to find how far from the left side of the text area the caret is (multiply number of characters on this line by the character width).
    • Find how far the text area is away from the left side of the window and the top side of the window.
    • Combine the widths and heights and find out where to show AutoComplete.
    I eventually did this, but it took many months of work and it was never 100% accurate. Furthermore, it was very hard to maintain. The algorithm for finding line breaks alone was over 600 lines of code, and it wasn't code that I was particularly proud of nor was it something I even showed interest in revisiting one day. It did its job and it performed well. If it was off by a little bit, it wasn't a huge deal...

    At least, until this year when I endeavored to rewrite NAMFox to clean up a lot of the "code smell" surrounding its design. I came to the logic for AutoComplete and wasn't quite sure how to improve what was already there. However, slowly but surely, I was able to reduce that 600-line monster to something just under 100 lines. Here I'll walk through each step of the code and explain every part.


    _getPopupPoint: function() {
    /// <summary>
    /// Determines the (x, y) coordinates at which to show
    /// the AutoComplete window.
    /// </summary>
    /// <returns type="Object">
    /// - x: The x-coordinate of where the window should be shown.
    /// - y: The y-coordinate of where the window should be shown.
    /// </returns>

    var text = this._textArea.value;

    // HACK: Firefox's key event doesn't expose any (x, y) coordinates
    // for us to harvest. As a result, we'll need to carry out
    // our own workaround. Both the x- and y-coordinate involve
    // the use of a temporary text area, as you'll see below.

    var dummyTextArea = $FX(this._htmlDocument.body).append(
    '<textarea id="namfox-auto-complete-temp-textarea" style="height: 2px;" />'
    ).find("#namfox-auto-complete-temp-textarea");


    So the function is called "_getPopupPoint", and it's more comments than code so far. this._textArea refers to the text area that's currently being processed (i.e. where the user is typing), and this._htmlDocument is the "document" object of the page.

    Where it gets interesting is the fact that we are creating a temporary text area (that's the <textarea id="namfox-auto-complete-temp-textarea"></textarea> HTML) and appending that to the document body. We'll use this to get both x- and y-coordinates. You'll see how next.


    // Y-Coordinate: The easier of the two. Grab all of the text that
    // appears before the caret and place it in a 2px-high text area
    // with the same width as the original text area. There are some
    // complications involving the presence of a scrollbar i.e. if
    // the original text area displays a scrollbar, then the dummy
    // text area must also display one, and vice versa.

    dummyTextArea.attr("value", text.substr(0, this._textArea.selectionStart));

    if (this._textArea.clientHeight !== this._textArea.scrollHeight) {
    // No scroll bar in the original; remove the one from the dummy.
    dummyTextArea.css("overflowY", "hidden");
    }

    dummyTextArea.css("width", this._textArea.clientWidth + "px");

    var caretOffsetTop = dummyTextArea.attr("scrollHeight");


    To find the height (y-coordinate), take all the text that appears before the caret and paste it in the dummy text area and then make the dummy text area the same width as the original text area. If we then get the scrollHeight of the dummy text area, that gives us the y-coordinate. :)


    // X-Coordinate: The harder of the two. Grab the text on this
    // line using the nsISelectionController of the text area's editor.
    // Then place this text in a 2px-wide text area, remove word wrapping,
    // and grab the scroll width.

    var selectionStart = this._textArea.selectionStart;
    var selectionEnd = this._textArea.selectionEnd;

    var selectionController = this._textArea.
    QueryInterface($("nsIDOMNSEditableElement")).
    editor.
    selectionController;

    // Move the caret to the beginning of the line.
    selectionController.intraLineMove(false, false);

    // Place the current line (up to the caret) inside the text area.
    dummyTextArea.attr(
    "value",
    text.substring(this._textArea.selectionStart, selectionStart)
    );
    dummyTextArea.css("width", "2px");

    // Eliminate word wrapping.
    var plainTextEditor = dummyTextArea.
    get(0).
    QueryInterface($("nsIDOMNSEditableElement")).
    editor.
    QueryInterface($("nsIPlaintextEditor"));
    plainTextEditor.wrapWidth = -1;

    var caretOffsetLeft = dummyTextArea.attr("scrollWidth");

    // Reset the selection
    this._textArea.setSelectionRange(selectionStart, selectionEnd);


    The x-coordinate is a little more difficult because it requires us to know how far the caret is from the left edge on the current line. Fortunately there is a way with the Firefox XPCOM model to find the index at which the current line starts (selectionController.intraLineMove). Replacing the text in the dummy text area with the text on this line and then changing that text area's width to something very small (2 pixels, in this case) allows us to use the same trick as the y-coordinate.

    There is one thing you have to watch out for, and that is the fact that words wrap in a text area! So if you set the width to something very small, you can't just use the scrollWidth as is.

    Fortunately, the XPCOM objects also allow us to disable word wrap (wrapWidth = -1). Afterwards, we can use the scrollWidth like before. From here it's all smooth sailing.


    // Now we have the offsets from the side of the text area,
    // grab the offsets from the edge of the viewport.
    // This is much easier. :)

    var textAreaOffsetLeft = this._textArea.offsetLeft;
    var textAreaOffsetTop = this._textArea.offsetTop;

    var offsetParent = this._textArea.offsetParent;
    while (offsetParent && offsetParent.nodeName !== "#document") {
    textAreaOffsetLeft += offsetParent.offsetLeft;
    textAreaOffsetTop += offsetParent.offsetTop;
    offsetParent = offsetParent.offsetParent;
    }

    // Combine all the information we have to make (x, y) coordinates.
    var offsetLeft = Math.max(
    caretOffsetLeft + textAreaOffsetLeft - this._textArea.scrollLeft,
    textAreaOffsetLeft
    );
    var offsetTop = Math.max(
    caretOffsetTop + textAreaOffsetTop - this._textArea.scrollTop,
    textAreaOffsetTop
    );

    dummyTextArea.remove();

    // Add 20 to the offsetTop because we want it a little further below
    // the caret, not at the caret itself.
    return {
    x: offsetLeft,
    y: offsetTop + 20
    };


    Now we've got the lengths from the caret to the left and the top of the text area, we need the lengths from the text area edges to the left and top of the page. We find this using the various offset properties.

    Finally, combine the lengths and return the point. We're done!




    My only wish is that this algorithm were cross-browser compatible. It would be awesome to actually see web sites featuring an AutoComplete, but I guess we will have to wait a bit longer for that...

    web development technology neoseeker related namfox autocomplete javascript

    Read more

    I'm not one for melodrama so I'll keep this short. I've gotten to a point where my work on this new version of NAMFox has become sickeningly excessive; I want to finish it as soon as possible.

    Consequently, I'm going to seclude myself from any distractions or even the more enjoyable parts of the Neoseeker milieu until it's done. If you need to contact me urgently, use PM. Obviously creating NAMFox requires me to use Neoseeker, so I suspect that I will be responsive in matters of high importance. I'll still fulfill my moderating duties, too.

    I'm estimating it will take at most 3 weeks.

    neoseeker related namfox neoseeker

    If you can find beauty in any sort of art (no pun intended =) ), then I'm sure you can forgive me for the title of this post.

    Yesterday I spent a significant amount of time re-writing the quick reply feature in NAMFox. Though there were a few changes to its looks (see below), most of the re-write was part of a re-architecture sprint in which I am deeply ingrained. This adventure has been a long and time-consuming one (over 130 hours since mid-December), and I'm glad to say I'm past the half-way point.

    Now, quick reply, what was wrong with it before? Two things come to mind:
    1. The HTML was horrible. It in no way resembled any semblance of a clean structure, one which lends itself so well to styling (e.g. http://www.csszengarden.com).
    2. The code was also horrible. Because of the warped structure of the HTML it was difficult to interact with the elements appropriately, often employing non-intuitive means to show things like the preview or error messages.
    Remember that I'm criticizing from the point-of-view of maintainability. The quick reply that's in today's NAMFox was designed to allow "quick replies", and I think it does that job very well.

    For the rest of the post I'm just going to post code samples to compare and contrast the code for some of the quick reply functions between the old version and my rewrite.


    1. Building Quick Reply

    This first excerpt relates to assembling the quick reply to display on the page. The old code creates the appropriate elements one at a time and styles them individually. The new version...well, it's a lot more intuitive.

    Old


      lineDiv = contextDocument.createElement("DIV");
      lineDiv.align = "left";

      paddedDiv = contextDocument.createElement("DIV");
      paddedDiv.style.paddingLeft = "2%";

      textAreaWidth = Configuration.getQuickReplyTextAreaWidth();

      // Create place for custom forum settings text to appear.
      replyMessageDiv = contextDocument.createElement("div");
      replyMessageDiv.id = "customReplyMessage";
      replyMessageDiv.style.width = textAreaWidth;

      paddedDiv.appendChild(replyMessageDiv);

      replyMessageBreak = contextDocument.createElement("br");
      replyMessageBreak.style.lineHeight = "0.5em";

      paddedDiv.appendChild(replyMessageBreak);

      // Create the Quick Reply text area and append it to the container.
      quickReply = contextDocument.createElement("textArea");
      quickReply.id = QuickReplyID;
      quickReply.name = "body";
      quickReply.rows = Configuration.getQuickReplyTextAreaHeight();
      quickReply.style.width = textAreaWidth;
      quickReply.wrap = "PHYSICAL";
      paddedDiv.appendChild(quickReply);

    New


      var html = "";
      html += '<div id=""namfox-quick-reply-container"">';
      html += ' <h4>NAMFox Quick Reply</h4>';
      html += ' <div id=""namfox-quick-reply-response-message""></div>';
      html += ' <div id=""namfox-quick-reply-core-container"">';
      html += ' <div id=""namfox-post-message-container""></div>';
      html += ' <div>';
      html += ' <textarea id=""namfox-quick-reply-textarea""></textarea>';
      html += ' </div>';
      html += ' <div id=""namfox-quick-reply-markup-bar""></div>';
      html += ' </div>';
      html += ' <div id=""namfox-quick-reply-options-container"">';
      html += ' <input id=""namfox-quick-reply-signature"" type=""checkbox"" />';
      html += ' <label for=""namfox-quick-reply-signature"">Append signature to this post [ members only ]</label>';
      html += ' </div>';
      html += ' <div id=""namfox-quick-reply-submission"">';
      html += ' <img id=""namfox-quick-reply-loading-image"" style="&quot;display:" />';
      html += ' <span id=""namfox-quick-reply-status""></span>';
      html += ' </div>';
      html += ' <div id=""namfox-quick-reply-action-container"">';
      html += ' <div id=""namfox-quick-reply-normal-action"">';
      html += ' <input id=""namfox-quick-reply-submit"" type=""button"" value=""Quick" />';
      html += ' <input id=""namfox-quick-reply-quick-preview"" type=""button"" value=""Preview"" />';
      html += ' <input id=""namfox-quick-reply-full-preview"" type=""button"" value=""Go" to="to" full="full" />';
      html += ' </div>';
      html += ' </div>';
      html += '</div>';

      this._$("script[src$=/forums/js/AjaxRequest.js]").before(html);

      // Static styling

      this._$("head").append(
      ''
      );

      // Dynamic styling (based off configuration preferences)

      this._$("#namfox-quick-reply-container").css("marginRight", ((100 - parseInt(config.textAreaWidth.substr(0, 2))) - 2) + "%");
      this._$("#namfox-quick-reply-textarea").attr("rows", config.textAreaHeight);
      this._$("#namfox-quick-reply-signature").attr("checked", config.useSignatureChecked);

    Comparison

    Wow, what a difference. Instead of wading through hundreds of lines of code to figure out what strange amalgam of HTML elements I've managed to put together, I can see at a glance the skeleton of the NAMFox quick reply. All of the static styles are applied via an external stylesheet, and all dynamic styles (like the height and width of the quick reply) can be applied very easily.

    The astute reader will probably notice the use of the "_$" function to do some pretty nifty DOM selection. A couple of posts ago I mentioned borrowing ideas from jQuery's API to do a lot of the DOM manipulation in NAMFox. Well, the parameter to the _$ function is a selector, that allows me to select just about anything I want. There is a problem, though, in that no one has made a jQuery-like API that works well for Firefox extensions. The "document" object in Firefox add-on world is not an HTMLDocument representing the current page; instead it is an XULDocument representing the chrome (i.e. the Firefox window itself, including the menus, toolbars, browser tabs, status bar, and so on). If you want the current HTMLDocument, you need to use the global "content.document" property; however, using that bit Oz and me hard back in the earlier versions of NAMFox (around 0.5) because users can switch tabs at any time. This can really screw you up if there is any chance of a delay in between a user's action and your code's response. Imagine the following scenario:
    1. User submits quick reply.
    2. NAMFox code submits quick reply and looks at the response for the new post's HTML.
    3. NAMFox code finds post HTML and inserts it on the same page.
    If we were using content.document and the user switched tabs in between steps 2 and 3, then we would add the post to the wrong page.

    So I digress to the original point—the purpose of the this._$() function to provide jQuery-like selection capabilities on the current document at all times. Unfortunately, to support this to my liking I had to rewrite a lot of jQuery myself. I've called it FXDom to reflect its purpose (Firefox DOM); it is not as vast as jQuery, but it definitely supports a lot of the scenarios I need it for. And it enables lovely, concise code like that above. After all that HTML building, I find the script tag that imports Neoseeker's AJAX features (/forums/js/AjaxRequest.js) and insert the quick reply before it:

    this._$("script[src$=/forums/js/AjaxRequest.js]").before(html);



    2. Submitting the Quick Reply

    This section highlights some of the changes in the NAMFox AJAX API. If you're not familiar with the term AJAX, think of it as a paradigm that enables asynchronous operations, which are used to perform tasks that could take a long time but do not lock up the user from doing other things on the screen. (Disclaimer: This is a very simple explanation!) So with AJAX, we can actually act like a browser and go to various pages without you seeing us. An example of such an asynchronous operation is to submit the quick reply and get the HTML for the post back from Neoseeker. So...here's the code.

    Old


      var body, // The Unicode URL-encoded post body to post.
      subject, // The subject of the thread.
      postVars; // The post variables to send to the page.

      disableButtons();

      context.pageController.addLineBreak(lineDiv, null);

      // Indicate that we are doing an async operation.
      loadingImage = Ajax.getLoadingImage("white", contextDocument);
      lineDiv.appendChild(loadingImage);

      // Assemble the AJAX request to make the reply.
      body = Encoding.unicodeUrlEncode(quickReply.value);

      subject = context.pageController.getSubject();
      subject = subject.replace(/^(re: )*(.*)$/, "$1$2");

      if (subject.indexOf("re:") !== 0) {
      subject = "re: " + subject;
      }

      subject = Encoding.asciiUrlEncode(subject);

      postVars = "subject=" + subject + "&body=" + body + "&submit=true&do=submit&discussion="
      + (sigCheckbox.checked ? "&usesig=Y" : "") + "&function=post_reply&threadid=" + threadId;

      Debug.write("Submitting quick reply for " + contextDocument.location.href);

      // When the reply is submitted, do other operations (see quickReplySubmitted).
      Ajax.post("http://www.neoseeker.com/forums/index.php", quickReplySubmitted, postVars);

    New


      this._beginProcess();
      this._reportStatus("Submitting reply...");

      var postData = {
      subject: $.urlEncode(this._normalizeSubject()),
      body: $.urlEncode(this._textArea.attr("value")),
      submit: true,
      "do": "submit",
      "function": "post_reply",
      threadid: this._threadData.threadId
      };

      if (this._$("#namfox-quick-reply-signature").attr("checked")) {
      postData.usesig = "Y";
      }

      $.post(
      "http://www.neoseeker.com/forums/index.php",
      postData,
      $.createCallback(this._onReplySubmitted, this),
      $.createCallback(this._onError, this)
      );

    Comparison

    There are a few things worth mentioning here. The process for both pieces of code is the same: (1) show the spinning circle to indicate we're doing something, (2) prepare the AJAX request to send your post, and (3) sending the request. For me, the pieces of code are better factored in the new quick reply. The call to _beginProcess sets up the spinning circle and reverts all changes to the screen (like removing previews or error messages). There is a new piece in the quick reply that shows you exactly what it's doing, which is considered during the call to _reportStatus. Finally, the set-up for sending the post data is a lot different in the new code, where it is a JavaScript object literal, and the old code, where it is a string. Finally, the new code features an error callback function that handles the case when the AJAX request takes too long to complete. Yep, I <3 jQuery's ideas.


    3. Appending the New Post to the Page

    I still remember when I wrote this functionality the first time. I thought it would be neat to give quick reply the ability to put the new post directly on the page without any refresh whatsoever. Unfortunately, I think I wrote this during a Pirates of the Carribean movie-fest, and that severely affected my resourcefulness. See below...

      var responseText, // The response HTML (in text) from the posted page.
      regexp, // A regexp to match all post rows in the page.
      results, // The results of that regexp match
      resultLength, // The number of results that matched.
      postRegexp, // Matches once for every post on the page.
      frozenPosts, // The number of posts on the current page (seen by the user)
      currentPosts, // The number of posts on the current page (seen by everyone else after refresh).
      postContent, // The content of the post itself.
      avatarTrElement, // The table row element that contains the avatar.
      bgColor, // The background color of the new post.
      id, // The ID of the table row for the avatar.
      avatarTdElement, // The table cell element that contains the avatar.
      avatarHeadlineTdElement, // The headline table cell (contains data posted, etc.)
      postTrElement, // The table row element which contains the post content.
      postTdElement, // The table cell element that contains the post content.
      barTrElement, // The table row element which contains functions like quote/report/edit, etc.
      tableThreadList, // The table which contains all posts on the current page.
      tbody, // The tbody element underneath the tableThreadList table.
      quickQuoteData, // An array of elements used for adding quick quote to the div bar under a post.
      quickEditData, // An array of elements used for adding quick edit to the div bar under a post.
      quickEdit, // A quick edit instance that adds functionality to the new post.
      oldPmForms, // The old PM forms in a page before the quick reply is added.
      pmForms; // The new PM forms in a page after the quick reply is added.

      oldPmForms = Document.getElementsByClassName(contextDocument, "pmform");

      Debug.write("Getting reply markup for latest post in thread " + threadId);

      responseText = xmlHttpRequest.responseText;
      Debug.write("Response text: " + responseText);

      regexp = /<tr id="".*?"[\s\S]*?<\/tr">[\s\S]*?<\/tr>[\s\S]*?(?:report|merge up)<\/a>\r?\n?<\/td><\/tr>/gi;
      results = responseText.match(regexp);
      resultLength = results.length;

      // Check that no other posts have come in before using quick reply.
      postRegexp = /<tr id=""\d+"/g;" frozenposts="contextDocument.body.innerHTML.match(postRegexp);" currentposts="responseText.match(postRegexp);" the="the" current="current" post="post" number="number" should="should" be="be" more="more" the="the" addition="addition" of="of" that="that" was="was" just="just" than="than"></tr>color]
      // number of posts currently on the page.
      if (resultLength > 0 && responseText.match(/<tr id=""row14"/)">== null && frozenPosts.length + 1 === currentPosts.length)
      {
      Debug.write("Adding markup automatically for quick reply in thread " + threadId);

      postContent = results[resultLength - 1];

      Debug.write(postContent);
      avatarTrElement = contextDocument.createElement("tr");
      bgColor = /color]>/.exec(postContent)[1];
      avatarTrElement.bgColor = bgColor;
      id = /<tr id=""(.*?)"/.exec(postContent)[1];"> /<tr id="".*?"" bgcolor="".*?"">([\s\S]*?)<\/tr>/.exec(postContent)[1];
      avatarTdElement = contextDocument.createElement("td");
      avatarTdElement.className = "authorcol";
      avatarTdElement.rowSpan = 3;
      avatarTdElement.innerHTML = /<td rowspan=""3"">([\s\S]*?)<\/td>/.exec(postContent)[1];
      Debug.write("Avatar element: " + avatarTdElement.innerHTML);

      avatarHeadlineTdElement = contextDocument.createElement("td");
      avatarHeadlineTdElement.id = "row5";
      avatarHeadlineTdElement.innerHTML = /<td id=""row\d+"">([\s\S]*?)<\/td>/.exec(postContent)[1];
      avatarTrElement.appendChild(contextDocument.createTextNode("\n"));
      avatarTrElement.appendChild(avatarTdElement);
      avatarTrElement.appendChild(contextDocument.createTextNode("\n"));
      avatarTrElement.appendChild(avatarHeadlineTdElement);

      Debug.write("Headline element: " + avatarHeadlineTdElement.innerHTML);

      postTrElement = contextDocument.createElement("tr");
      postTrElement.bgColor = bgColor;
      postTdElement = contextDocument.createElement("td");
      postTdElement.innerHTML = /<tr bgcolor="".*?"">([\s\S]*)<\/tr>(?=[\s\S]*?<\/tr>)/.
      exec(postContent)[1].
      replace(
      /<div style="&quot;display:"></div>color]>/g,
      "<div class="\"spoiler_header\"" style="&quot;display:">"
      );
      Debug.write("Post: " + postTdElement.innerHTML);

      // Adds double click functionality, if configured to do so.
      quickEdit = new QuickEdit(context);
      quickEdit.addDoubleClick(postTdElement);
      postTrElement.appendChild(postTdElement);

      barTrElement = contextDocument.createElement("tr");
      barTrElement.bgColor = bgColor;
      barTdElement = contextDocument.createElement("td");
      barTdElement.className = "small";
      barTdElement.align = "right";
      barTdElement.innerHTML = /<tr bgcolor="".*?"">(<td align=""right"">[\s\S]*?)<\/tr>/.exec(postContent)[1];

      // Insert custom div bar links. (quick quote and quick edit)
      quickQuoteData = new QuickQuote(context).createLink();
      quickEditData = quickEdit.createLink();

      // Remove the old quick quote/quick edit links.
      context.pageController.removeDivBarLinkFromCell("quick edit", barTdElement);
      context.pageController.removeDivBarLinkFromCell("quick quote", barTdElement);

      // Add the new quick quote and quick edit links.
      for (var i = quickEditData.length - 1; i >= 0; --i)
      {
      barTdElement.insertBefore(quickEditData[i], barTdElement.firstChild);
      }

      for (var i = quickQuoteData.length - 1; i >= 0; --i)
      {
      barTdElement.insertBefore(quickQuoteData[i], barTdElement.firstChild);
      }

      barTrElement.appendChild(barTdElement);

      // Add the new post to the existing page.
      tableThreadList = contextDocument.getElementById("threadlist");
      tbody = tableThreadList.childNodes[2];
      tbody.insertBefore(barTrElement, Document.getElementsByClassName(contextDocument, "threadlistbar")[1]);
      tbody.insertBefore(contextDocument.createTextNode("\n"), barTrElement);
      tbody.insertBefore(postTrElement, barTrElement.previousSibling);
      tbody.insertBefore(contextDocument.createTextNode("\n"), postTrElement);
      tbody.insertBefore(avatarTrElement, postTrElement.previousSibling);

      // Add the Quick PM extensions to the the PM icon just generated
      pmForms = Document.getElementsByClassName(contextDocument, "pmform");
      if (pmForms.length > oldPmForms.length)
      {
      context.extensionData["QuickPM"].addListener(pmForms[pmForms.length - 1]);
      }

      // Clear the quick reply text area, and revert all state changes--
      // as if the user just refreshed the page.
      quickReply.value = "";
      enableButtons();
      lineDiv.removeChild(loadingImage.previousSibling);
      lineDiv.removeChild(loadingImage);
      }
      else
      {
      // Fallback in case there have been replies since the last refresh,
      // or the post needs to go on a new page.
      Debug.write("Refreshing the page. There have been posts since the last refresh or this post goes on a new page for thread " + threadId);
      contextDocument.location.href = "http://" + context.domain + "/forums/lastreply.php?t=" + threadId;
      }

    New


      var response = xhr.responseText;
      this._reportStatus("Adding reply to page...");

      var oldPosts = this._htmlDocument.body.innerHTML.match(this._postRegExp);
      var newPosts = response.match(this._postRegExp);

      // If the td with id "row14" exists on the current page, then no more replies
      // can fit on the current page. Thus it's pointless to try to retrieve the
      // reply from the response text.
      if (newPosts.length > 1 && this._htmlDocument.body.innerHTML.match(/<td id=""row14"/)">== null && newPosts.length === oldPosts.length + 1) {
      try {
      var conditional = this._postRegExp.exec(response);

      // Try to find the last post ID by continuously executing the post regular
      // expression against the response text.
      var lastPostHeader = null;
      for ( ; conditional; conditional = this._postRegExp.exec(response)) {
      lastPostHeader = conditional;
      }

      $.assert(lastPostHeader, "lastPostHeader, The last post must exist on a page.");

      var lastPostId = lastPostHeader[1];
      var postHtml = response.match(new RegExp('<tr id=""'" lastpostid="lastpostid" bgcolor="".*?"">[\\s\\S]*?(?=<tr class=""thread)'))[0];" quick="quick" edit="edit" for="for" the="the" new="new"></tr>color]
      }
      catch (e) {
      $.error(e);
      this._htmlDocument.location.href = this._getLastReplyUrl();
      }
      finally {
      this._clearTextArea();
      this._endProcess();
      }
      }
      else {
      // If any of the conditions fail, then fallback to showing the last reply.
      this._htmlDocument.location.href = this[/color]._getLastReplyUrl();
      }

    Comparison

    Granted, this isn't exactly a fair comparison because the new code doesn't currently add quick edit, quick quote, and quick PM enhancements to the new post, but still, I look back at the old and say "What the *bleep* was I thinking?" Besides my smarter use of regular expressions to find the new post contents, FXDom once again comes to the rescue by allow me to place that HTML right before the last element that has a CSS class "threadlistbar".

    I haven't decided on the best way to add the new "quick" suite of features to the new post yet, but I'm sure FXDom will come in very handy there, too.




    The work I put in yesterday was well-worth it. This clean-up yielded a smaller amount of code to maintain (452 "lines" vs. 772 "lines") as well as a certain elegance that I could never achieve in the old code base. And that's why it's sexy.

    Before I forget, here's a picture of the new quick reply as it stands. No guarantees it'll stay like this for v1.1:

    </tr></td></td></tr></div></tr></td></td></tr></tr></tr></tr>

    technology neoseeker related namfox fxdom javascript

    Read more

    Quick update on the blog front!

    I released NAMFox 1.0.5 about a half hour ago to fix the broken quick reply feature. Unfortunately due to a very subtle change in the HTML rendered by Neoseeker, it appeared as if quick reply never submitted the post until you refreshed the page. It should be smooth sailing from here since it's now fixed.

    I wish I had noticed this sooner, but since I was in the middle of my move to Seattle, I wasn't spending much time on Neoseeker. This goes to show that I really depend on your feedback to help keep NAMFox great. I am sure there were a few of you out there who noticed this before but hesitated to report it. If you didn't report it, I'd like to know why to see if there is anything I can do to make the process easier in future.

    Thanks!

    More Release Details

    technology neoseeker related namfox

    When I participated regularly in the Neoseeker IRC chat, Dynamite would have no shame in poking fun at me because I was heavily involved in software development using the Microsoft .NET platform (specifically C#). This is most likely because Dynamite doesn't like Windows (and accordingly, doesn't like Microsoft), but that's another story entirely.

    Sometimes he would joke about my working for Microsoft. Well, Dynamite, I am proud to tell you that I have accepted a software engineering job with Microsoft starting at the end of this month. I am joining a team that develops a portion of the .NET platform, so this is a fantastic opportunity for me to help be a part of a framework I've been using for almost four years now. I am very excited to see what's in store for me up there. Seattle will definitely be a huge change from where I am currently situated, and I can only hope to continue learning as much as I can to become a better software engineer.

    On the Neoseeker front, I don't believe this will change much. NAMFox will still be my night hobby, and I will continue to moderate. Of course, I don't fully know what to expect, so that may change...

    musingsthoughts microsoft career

    Read more

    It seems that I can't go very long without running into some poor soul who carries the burden of saving the human race from the Internet Explorer web browser. Oftentimes, said person's strategy is to cast IE users* as degenerates, hoping that his intimidation tactics will scare them into using some other BETTER browser, which usually happens to start with "M" and rhyme with "Showvilla Mirelocks." I hope I'm wrong, but I'd be willing to bet that most of these folks don't have anything constructive to say on why using IE makes you an imbecile.

    My advice? Go with what you're happy with. If you like Internet Explorer, why go through the hassle of learning a new browser? Same thing goes for Firefox, Opera, Safari, Konqueror, Chrome, or even Lynx! If you are going to switch browsers, do it for tangible reasons that are actually worth your learning a new environment, not because your friend down the street thinks your browser is the devil.

    tl;dr - Pushing your browser preferences onto other people almost always makes you look like a giant tool.

    * Note that this post is about users, not web site developers who have to deal with the idiosyncrasies of the IE family. :)

    Read more

    For those of you who talk to me on MSN or Google Talk, you may notice that I keep a word of the day in my personal message. Some ask where the word of the day comes from. The truth is that every day's word comes from my playing around on Free Rice. If you haven't heard of it, I would recommend checking it out. I treat it as a great way to learn vocabulary while doing some good for the world.

    I'm just a little bit proud of myself that I've been able to contribute 100,000 grains of rice on the site:



    Here's to another 100K!

    P.S. Today's word of the day is acanthoid; it means spiny. :)

    P.P.S. This may be a repeat post. I don't think Neoseeker likes it when you start a post title with a number...(Or maybe something else was hosed. :))

    free rice musingsthoughts

    It's that time again—abandon all hope ye who enter here, unless you found yourself interested in the first deep dive, of course.

    In the first deep dive I mentioned that in NAMFox's re-architecture I needed to "build software on the Firefox platform in the way it was meant to be built," and that's what I want to explore today. I'll be formatting this and future blog posts on the topic into Problem/Solution-type scenarios, so it's easier to track what I want to achieve.

    Problem

    In the current build of NAMFox there are several JavaScript utility classes that lend themselves well to being called "services." In Firefox's current add-on model, these services will be created once in every window in the browser, which is not ideal because it hampers their flexibility and thus, their capability to be extended.

    Explanation

    To run JavaScript in an add-on, some piece of the user interface (UI) needs to reference it. In this case, the NAMFox toolbar (the UI) references multiple script files:

    <overlay id="NMMenu_Overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    <script type="application/x-javascript" src="chrome://namfox/content/utilities/Ajax.js" />
    <script type="application/x-javascript" src="chrome://namfox/content/utilities/Configuration.js" />
    <script type="application/x-javascript" src="chrome://namfox/content/utilities/Convert.js" />
    <script type="application/x-javascript" src="chrome://namfox/content/utilities/Debug.js" />
    </overlay>

    This also means that each piece of JavaScript runs in its own UI context, so when the preferences window is launched, it also must reference multiple script files:

    <prefwindow title="NAMFox Preferences" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    <script type="application/x-javascript" src="chrome://namfox/content/utilities/Configuration.js" />
    <script type="application/x-javascript" src="chrome://namfox/content/utilities/Convert.js" />
    <script type="application/x-javascript" src="chrome://namfox/content/utilities/Debug.js" />
    </prefwindow>

    Notice any similarities? As in...the same files are imported multiple times? While not necessarily a problem, it does mean that the browser has to load and execute each of these scripts many more times than it needs to. Most of these utility classes contain singleton objects that act as services. To show you what I mean, here's an excerpt from the Ajax class (with all functionality stripped):

    var Ajax = function() {
    /**
    * Invokes a page using a specific method, specifying a handler to handle the onreadystatechanged event
    * of the XmlHttpRequest object.
    *
    * @param method Either "GET" or "POST"
    * @param url The url to get data from or post data to.
    * @param callback A callback function that will respond to the onreadystatechanged event of the XmlHttpRequest object
    * @param async Specifies whether the GET/POST should be asynchronous or not. The default is true.
    * @param postVars A query-string-like string which contains information we need to send to the page if using the "POST" method.
    * @author Artificer
    */
    function invoke(method, url, callback, async, postVars, bypassCache) {
    }

    return {
    get: function (url, callback, async, bypassCache) {
    },
    post: function (url, callback, postVars, async, bypassCache) {
    }
    };
    }();

    Not super exciting but it gets the job done. It provides a simple mechanism to get data from a URL and post data to a URL. The invoke function is a helper function used by both Ajax.get and Ajax.post.

    I really don't need this object to exist in multiple contexts! What if later on I wanted to keep track of how many AJAX calls NAMFox made during browser sessions? I can't, because I would have a counter in each of the following contexts (or red box, if you will):



    What we want is to enlarge that box:

    Enter XPCOM

    I alluded to XPCOM in the previous post as a way to enable the scenario I described i.e. making my services exist in only one location. In addition, it will help to get rid of a lot of these nasty utility files. :)



    It's not particularly intuitive to go from a JavaScript singleton to an XPCOM component, so I'll do my best to explain the process here. First we must create an interface which tells Firefox which attributes and functions we intend to expose to consumers of our component—in this case, the other pieces of code that want to harness the AJAX functionality we encapsulate. The syntax for this interface is [https://developer.mozilla.org/en/XPIDL XPIDL], or Cross-Platform Interface Definition Language. You can look at the link for details on the syntax; here's what the new AJAX interface looks like.

    /// <summary>
    /// A success callback interface, whose notify method is invoked when an AJAX call is successful.
    /// </summary>
    [scriptable, function, uuid(B391D38F-188E-4015-9D86-CE59D2BA79F4)]
    interface nfxIAjaxSuccessListener
    {
    /// <summary>
    /// Notifies the listener that the specified request succeeded.
    /// </summary>
    /// <param name="request" type="nsIXMLHttpRequest">The request which succeeded.</param>
    void notify(in nsIXMLHttpRequest request);
    };

    /// <summary>
    /// An error callback interface, whose notify method is invoked when an AJAX call
    /// encounters an error that prevents the data from being retrieved.
    /// </summary>
    [scriptable, function, uuid(24CC9547-092A-4CC4-9696-3FE794EA4674)]
    interface nfxIAjaxErrorListener
    {
    /// <summary>
    /// Notifies the listener that the specified request failed.
    /// </summary>
    /// <param name="request" type="nsIXMLHttpRequest">The request which failed.</param>
    /// <param name="causedByTimeout" type="boolean">Specifies whether the error was actually a request timeout.</param>
    void notify(in nsIXMLHttpRequest request, in boolean causedByTimeout);
    };

    /// <summary>
    /// Allows callers to request data asychronously from remote resources.
    /// </summary>
    [scriptable, uuid(0BAE99E0-BE9B-4499-BEFB-3BEB898CAA4F)]
    interface nfxIAjax : nsISupports
    {
    /// <summary>
    /// Indicates that the caller wants to use the Firefox cache to retrieve data.
    /// </summary>
    const short UseCache = 0;

    /// <summary>
    /// Indicates that the caller explicitly does not want to use the Firefox cache when retrieving data.
    /// </summary>
    const short BypassCache = 1;

    /// <summary>
    /// Asychronously gets the data at a specific URL.
    /// </summary>
    /// <param name="url" type="string">The URL against which the request will be made.</param>
    /// <param name="data" type="object">A object of data to send.</param>
    /// <param name="success" type="nfxIAjaxSuccessListener">The success callback.</param>
    /// <param name="error" type="nfxIAjaxErrorListener">The error callback.</param>
    /// <param name="cacheHandling" type="int">Specifies how to handle Firefox's cache when making a request.</param>
    /// <returns type="nsIXMLHttpRequest" />
    nsIXMLHttpRequest get(in string url, in nsISupports data, in nfxIAjaxSuccessListener success, in nfxIAjaxErrorListener error, in short cacheHandling);

    /// <summary>
    /// Asychronously posts data to the specified URL.
    /// </summary>
    /// <param name="url" type="string">The URL against which the request will be made.</param>
    /// <param name="data" type="object">An object of data to send.</param>
    /// <param name="success" type="nfxIAjaxSuccessListener">The success callback.</param>
    /// <param name="error" type="nfxIAjaxErrorListener">The error callback.</param>
    /// <param name="cacheHandling" type="int">Specifies how to handle Firefox's cache when making a request.</param>
    /// <returns type="nsIXMLHttpRequest" />
    nsIXMLHttpRequest post(in string url, in nsISupports data, in nfxIAjaxSuccessListener success, in nfxIAjaxErrorListener error, in short cacheHandling);
    };

    If you ignore the nfxIAjaxSuccessListener and nfxIAjaxErrorListener, then this interface is not difficult to understand. (Don't worry, we'll look at the listeners later.) The interface itself is scriptable (accessible by JavaScript) and has its own ID. (Every interface must have a Universally Unique Identifier.) There are two constants that specify how to handle caching (UseCache and BypassCache) that are used as parameters to the get and post functions. Now that you are in XPCOM land, you no longer have access to the ubiquitous JavaScript objects like XMLHttpRequest or Number. Instead you must use built-in XPIDL data types (string, boolean, short, etc.) or other interfaces (nsIXMLHttpRequest, nsISupports, etc.). If we look at the get function, it takes five parameters: the URL to get, arbitrary data to send to the URL (that becomes part of the query string), two listeners, and a short indicating how to interact with Firefox's cache. After it executes the AJAX call, it will return the nsIXMLHttpRequest instance that it uses to create the AJAX request. You can think of this as the XMLHttpRequest object with a few bonus properties and methods. Not too bad, right?

    The listeners are used to callback from XPCOM into normal JavaScript. Because each listener has a "function" attribute applied it means we can pass in a function from JavaScript, and XPCOM will be able to invoke that function by call the notify method. If you're not quite following, wait till the end where I show an example of how to call the get function from normal JavaScript. Then it should make more sense.

    The second step is to implement the component, because all you currently have is an interface. If you're doing this on your own it's more difficult to actually register the component with Firefox than it is to implement the component. The implementation of the Ajax component is not trivial, so I won't post it, but you may have a look at it and other files mentioned in this post with the links at the end of this post.

    function Ajax() { // implements nfxIAjax
    }

    Ajax.UseCache = 0;
    Ajax.IgnoreCache = 1;

    Ajax.prototype = {
    get: function(url, data, success, error, cacheHandling) {
    /// <summary>Gets data at a specific URL.</summary>
    /// <param name="url" type="string">The URL against which the request will be made.</param>
    /// <param name="data" type="object">A object of data to send.</param>
    /// <param name="success" type="nfxIAjaxSuccessListener">The success callback.</param>
    /// <param name="error" type="nfxIAjaxErrorListener">The error callback.</param>
    /// <param name="cacheHandling" type="int">Specifies how to handle Firefox's cache when making a request.</param>
    /// <returns type="nsIXMLHttpRequest" />
    },

    post: function(url, data, success, error, cacheHandling) {
    /// <summary>Posts data to a specific URL.</summary>
    /// <param name="url" type="string">The URL against which the request will be made.</param>
    /// <param name="data" type="object">An object of data to send.</param>
    /// <param name="success" type="nfxIAjaxSuccessListener">The success callback.</param>
    /// <param name="error" type="nfxIAjaxErrorListener">The error callback.</param>
    /// <param name="cacheHandling" type="int">Specifies how to handle Firefox's cache when making a request.</param>
    /// <returns type="nsIXMLHttpRequest" />
    }
    };

    Obviously it would be more useful to actually fill in code to execute where get and post are, but again, for the purpose of this blog it is not important. The next step is to register the component. In NAMFox, this is fairly simple because I spent time doing it right a long time ago. If you scroll down to the "NAMFoxModule" in the same file, then you'll see something like this:

    /***********************************************************
    module definition (xpcom registration)
    ***********************************************************/
    var NAMFoxModule = {
    objects: {
    dictionary: {
    CID : Components.ID("{B95D09A2-943E-48d3-9D29-8001DAD3188D}"),
    contractID : "@namfox.neoseeker.com/dictionary;1",
    className : "NAMFox Dictionary",
    factory : createFactory(Dictionary, false)
    },
    ajax: {
    CID : Components.ID("{0BAE99E0-BE9B-4499-BEFB-3BEB898CAA4F}"),
    contractID : "@namfox.neoseeker.com/ajax;1",
    className : "NAMFox Ajax Utility",
    factory : createFactory(Ajax, true)
    }
    // And so on
    }

    To add a new XPCOM component implementation, all you need to do is add an object with four properties: the interface's UUID, the contract ID, a friendly name, and a factory to create the class. The UUID here matches the the UUID declared on the nfxIAjax interface. The contract ID is a way for consumers of the Ajax API to actually use this particular implementation. The friendly name is not used to my knowledge... The factory is out-of-scope for what I want to talk about here, but you may feel free to browse the code (linked at the end of this post).

    After this is done, you have an XPCOM component! And all you need to do to call from normal JavaScript (i.e. JavaScript running in the context of a piece of UI) is this:

    // Get the service from XPCOM. Use the contract ID (@namfox.neoseeker.com/ajax;1) to get the implementation
    // and the interface name to tell it which functionality you want to access. This is required because
    // implementations can implement multiple interfaces.
    var ajax = Components.classes["@namfox.neoseeker.com/ajax;1"].getService(Components.interfaces.nfxIAjax);

    // The strange object literal is passed as raw data. The wrappedJSObject property is used for XPCOM to access arbitrary data, which we need here.
    // The XPCOM component then uses this data and appends it to the query string (in case of GET) or to the HTTP request (in case of POST).
    // In this case we'll make a request to http://www.neoseeker.com/forums/index.php?fn=browse_pm&mailbox=received.
    // The functions for success and failure are marshalled as our nfxIAjaxSuccessListener and nfxIAjaxErrorListener, respectively.
    // To access the cache constants, we have to go back to the interface directly.
    ajax.get(
    "http://www.neoseeker.com/forums/index.php",
    {
    wrappedJSObject: {
    fn: "browse_pm",
    mailbox: "received"
    }
    },
    function(request) {
    alert("success");
    },
    function(request, causedByTimeout) {
    alert("failure");
    },
    Components.interfaces.nfxIAjax.UseCache
    );

    Looking at this code you may think "YUCK!" to yourself, especially because of the code to get the AJAX service and the code to send data for the query string. Eventually, though, a thin XPCOM façade will allow me to write code like this:

    $.get(
    "http://www.neoseeker.com/forums/index.php",
    {
    fn: "browse_pm",
    mailbox: "received"
    },
    function(request) {
    alert("success");
    },
    function(request, causedByTimeout) {
    alert("failure");
    },
    $.cacheHandling.useCache
    );

    Definitely worth it for me. The AJAX component is probably one of the more complicated ones so I really don't mind answering any questions you have on it. There are some other components to look at, like nfxITrace and nfxIDictionary, which are much simpler.

    In review, I walked through the reasons for creating an XPCOM component and what I'm planning to do with a lot of the existing NAMFox services to move them to XPCOM. I walked through the challenges of moving the Ajax service to XPCOM, including its interface and implementation. Finally, I touched on how to call the XPCOM component once you're done implementing it.

    Reference Files

    The NAMFox Toolbar (XUL)
    The NAMFox Preferences (XUL)
    Existing AJAX Service (JavaScript)
    AJAX XPCOM Interface (And Many Others) (XPIDL)
    AJAX XPCOM Implementation (And Many Others) (JavaScript)

    namfox software architecture technology neoseeker related

    Read more

    Enough of that techno-jargon for now...I'm in the middle of getting ready for a flight home tomorrow to spend some time with my family for Christmas till next Monday. The problem is that I will still have my laptop, so I'm sure some project will suck me in while I'm down there.

    And that is why I am writing this post. What are some things you do in order to avoid or put off work? About all I can think about now are video games, but I have only a Super NES down there, which as beautiful as it may be, it is definitely not an awe-inspiring system nowadays. I just need a project of sorts to occupy my mind while I'm down there, preferably not computer- or technology-related.

    Grazie!

    (I'm not really down; I just like the alliteration of "Winter Break Blues." Also, I had to add something to the title because it was fewer than 16 characters. Rebel, please.)

    musingsthoughts

    Today I spent some time getting NAMFox onto Google Code. Even though by the nature of Firefox extensions the project is "open-source," I didn't feel that it was living up to that name. Of course, I'm still the only person working on it, but I hope this encourages some of you that have shown interest in tweaking it to download and play around with it.

    http://code.google.com/p/namfox/

    (NAMFox 1.0.4 is in the /trunk folder, while my work on NAMFox 1.1 is in the /branches/Nov 2008 v1.1 Re-Architecture/ folder.)

    technology neoseeker related namfox google code

    So now that I have some spare time to work on NAMFox again (and what sweet spare time it is...), I thought about what I actually want to do with it in the next version. There are a lot of suggestions and bugs out there for me to take care of, so there are definitely things to do. However, as of late, I have been increasingly dissatisfied with the internal architecture of NAMFox.

    Most (useful) pieces of software are built from many different components working together to meet some need. If you think about it this way, then it's not a giant leap to compare software to cars. There are a lot of different parts that go into them, and none of those parts on their own meet the same need that the car as a whole does. Both can upgrade their parts to provide more value for the consumer, and both can lose value just by aging and hence, not staying up-to-date.

    One circumstance that drifts away from the analogy is when software upgrades, it may still provide additional value to consumers, but it can start to degenerate internally. As it becomes easier and easier for developers to "hack" new features rather than building them correctly, then the development cycle enters this spiral of horror until no one wants to touch it again. There are many horror stories involving real software that met this fate on The Daily WTF. It's funny when you're sitting on the outside, but you can get pretty desperate when you're on the inside.

    Fortunately, NAMFox is nowhere near that state, but I think now is a good time to re-architect it so that its parts are working more cohesively than ever before. My end goals are to (1) make maintainability easier and (2) provide a better environment for other people to make their own tweaks to the tool. This also means faster turnaround time for bug fixes and suggestions. Back in early 2007, I did much of the same thing with the code that Oz and I had been working on, and after learning yet another slew of new things, I'm ready to tackle the challenge again.

    So how do I achieve this? There are a couple of specific means I have in mind.
    1. Build software on the Firefox platform in the way it was meant to be built.

      When I started working on this tool, I was familiar with JavaScript in the browser, not JavaScript in a Firefox extension. As a result, most of the new code I added may have been great ideas for web pages, but not the best for extensions. Over time as I learned more about the extension model, I improved, but when there is already of lot of infrastructure built (especially one that works), one becomes wary of making large changes that could rock the boat.

    2. Disentangle the components' dependencies on each other.

      A major problem that leads to software rot is when many parts depend on each other to survive—that is, they cannot be separated from the infrastructure in which they are cemented. I feel that this is the case with too many of the current NAMFox components, especially in the areas around the user interface—markup buttons, drop downs, or simply just managing the HTML that NAMFox puts on the page. I think it's fine for dependencies among components to exist, but when they become too interleaved to see what depends on what, then it's time for a change.

    3. Utilize ideas from other tools and frameworks to build better software.

      Over the past couple of months, I have seen a few other developments in the software industry that have given me great ideas for how to make NAMFox better (internally). Specifically, researching and taking little bits and pieces from the architecture of jQuery, ASP.NET MVC, and even Firebug has yielded some interesting results.

      Just tonight someone messaged me about making a tweak in his own NAMFox, but he made a mistake in rebuilding the package; Firebug's build system uses Apache Ant, which is great because it's cross-platform and you could do a hell of a lot more stuff with it than with a batch file, which was what Oz and I were using. Although you have to download Java (collective groan) to run it, it's a phenomenal build system that probably will eventually let me automate deploying new versions to the web. (The manual process is tedious and error-prone.)

      ASP.NET MVC had a visible implementation of URL routing (similar to Apache's mod_rewrite) where requests that go to a certain URL are received by certain "handlers." Today I have a problem in that an event is raised when a page is loaded, and then I have to check about 50 different things to determine what I should do on that page. But now, I can restructure how functionality is added to pages. This means that this particular code goes away:

      code
      function onDOMContentLoaded(e) {
        var url = e.originalTarget.location.href;
        if (url.match(/forums\/\d+\/t\d+/))
        {
          pageController.addQuickThreadFunctionality(context);
        } 
        else if (url.match(/forums\/\d+/))
        {
          pageController.addQuickForumFunctionality(context);
        } 
        else if (url.match('edit_profile.html$') || fn == 'forum_settings')
        {
          //Add markup buttons to Forum Settings or guestbook
          pageController.addSignatureButtons();
        } 
        else if (url.match(/members\/guestbook/) && fn == 'sign')
        {
          pageController.addGuestbookButtons();
        }
      }
      // And so on...
      


      And I can replace it with something along these lines:

      code
      // SETUP
        var r = new RoutingHandler();
        r.addRoute(new Route("{subdomain}neoseeker.com/forums/{forumId}/t{threadId}-{*threadname}/", new ViewThreadHandler());
      
        // somewhere else...
      
        function ViewThreadHandler() {
        }
      
        ViewThreadHandler.prototype = {
            handleRequest: function(routeData) {
                // routeData has properties for all of
                // the wild cards in the route. For example:
                //  subdomain  -> "www."
                //  forumId    -> "115"
                //  threadId   -> "867089"
                //  threadName -> "sandbox"
              
                // Add Quick Reply, Quick Edit, etc.
            }
        };
      
      // Now handle requests.
      
        function onDOMContentLoaded(e) {
            r.handleRequest(e.originalTarget.location.href);
        }
      


      I can be as granular or as specific as I want with these patterns. This example showed one of the more complicated patterns, which matches the URL that appears whenever I view a thread on Neoseeker. This will make it much easier to add new functionality to other pages, because all I need to do is add a new route and handler for that URL, and BAM: we're golden.

      jQuery is a particularly quaint API for JavaScript developers that abstracts away a lot of the complexities and cross-browser inconsistencies of JavaScript that deters a lot of people from getting into JavaScript in the first place. (Look here for some tutorials.) I don't develop web sites, so I was never interested in jQuery for the longest time. What finally attracted me was its simplicity. Really, how cool is it that all you have to do to make every div on the page fade away when you click it is this:

      code
      $(function() {
          $("div").click(function() {
              $(this).fade("slow");
          });
      });
      


      Hundreds of lines of painful code reduced to fewer than 5. Awesome.

      So I thought why not bring the same sort of simplicity over to NAMFox? An example is the repetitive code you normally type to invoke an XPCOM component.1

      Before, to get the preferences service:
      code
      Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
      


      After:
      code
      $("@mozilla.org/preferences-service;1").service("nsIPrefService");
      


      So it's not as elegant as what the jQuery guys have put together, but simple time-savers like this can make working with NAMFox a much more pleasurable experience.
    I apologize that this got a little long, but I am genuinely excited about making these latest upgrades to NAMFox, the right way. I'll continue to blog more about these architecture changes more for myself as a journal that I can look back on, but I would be happy to answer any questions people have.

    1 XPCOM components are usually services which exist only once within the scope of a Firefox session, including across multiple windows. An example of one is the NAMFox Custom Message manager, which is the data store for custom messages; it's an XPCOM component because custom messages can't get out of sync across different windows. Otherwise, you can have some very frustrated people out there. :(

    namfox software architecture technology neoseeker related

    Read more



    Effort and courage are not enough without purpose and direction.



    In an unprecedented and from now on rare occurrence, I will be blogging about a topic I usually stay away from: me. But I think you will forgive me using this post as an aide mémoire for later down the tortuous road of life...(that's tortuous, not torturous!)

    Back in July I announced that I was taking a break from further NAMFox development because of some non-Neoseeker projects I needed to take care of. One of those projects was really my full-time job, where I have been privileged to work alongside some extremely smart and talented people helping the business for which we work to improve their competitive advantage through the software that we build. The other project was part of a Master of Science in Computer Science degree that I have been pursuing for the past year and a half. During the time I wasn't in a lecture class, I had this enjoyable (yet draining) side-project to occupy my time. This particular project has been on my plate since the winter quarter this year (January).

    After devoting almost a full month of time to my master's project this year, I have finished all of the requirements necessary to graduate. (Of course, that doesn't ensure that I will graduate until this final course is graded, but looking at this year's trends I don't think that will be a problem.) I have learned a significant amount this year from the work I've put in, and I definitely think it's paid off. I hate to be cryptic but a lot of this project revolves around improving frameworks and tools that Microsoft have built, something I'm not sure you would be particularly interested in. :P But maybe you've had the joy of working on a project for an extended period of time, and you started to see the results only toward the very end? That's how I feel—as if everything has finally come to fruition after all this time.

    After graduating, I will have a much more favorable position within the industry, and I'll also have more time. Best of both worlds!

    The reason I mention all of this is that I have to pay homage to Neoseeker, its founders and developers, and its developer community for sparking my interest in computer science and keeping it alive. 6 years ago I was one of thousands of members struggling with HTML and CSS (and back then, JavaScript) to build what I thought was the perfect NeoHome. Although I never got anywhere close to what I've done today, it was a fun hobby in which to involve myself. Still, I actually never considered going into a computer science career until I decided to attend a university which specialized in it. Ironically my first months there were spent doing much of the same HTML and CSS work that I had done years before on Neoseeker. Over the next couple of years, I began to learn more and more at school, and I took this back to Neoseeker to help as much as I could. That's eventually how I got involved with NAMFox and I suppose why I now moderate the HTML/CSS & NeoHome Help and Web Coding forums. And so we come to where I am today...

    A big thank you to Neoseeker for helping me to get started and to all of you who have helped me get to where I am today. Whether you are or have been a close friend or a technical colleague, thank you.

    P.S. I do look forward to working on NAMFox more again. In fact, I am doing so more and more now. :)

    computer science neoseeker education musingsthoughts technology neoseeker related

    Read more

    So I'm headed to Chicago tomorrow for a developer conference that will last a couple of days. Long story short—Microsoft is about to release updates for one of their product lines, and this is a preview of what to expect from the release. I'm pretty excited not only because of the learning and networking opportunities but also because I've never been to Chicago before.

    I'm packing tonight and want to be careful not to miss anything important. The only part that makes this trip weird (read: awkward) is that I'm probably going to spend Thursday night at the office just because I'll be back so late that night, and my car will already be there. I think I have everything I'll need, but I'm wondering if any of you guys have ever forgotten something important before a long trip...if so, what did you leave? Could help jog my memory. :)

    Thanks!

    musingsthoughts technology

    I spent some time today making a video tutorial on Firebug, an add-on for Mozilla Firefox that is amazingly helpful for web development. It can help you to cut a significant amount of time from whatever you make, whether it's a blog theme, NeoHome, or your own web site. Check it out!





    Thanks to Aya Chan for letting me use her blog as an example. And yes, I know my jokes are not funny. :(

    firebug tutorial html css firefox web development technology

    Read more

    If you couldn't tell already by the content of this blog, I work in Information Technology. One of the sites I've enjoyed visiting since I joined this profession is The Daily WTF, where many different stories point out the quirks of people and products in this industry.

    If you are tech-savvy, then I guarantee you'll get some laughs out of it. If not, don't worry, you don't have to be! You can still have a few laughs at some of the visual articles. :)

    The interviews are also a treat.

    Check it out!

    technology

    I admit that few things feel better than coming up with a color scheme that works and works well, but for the rest of us mortals there is a fantastic site from Adobe Labs called Kuler.

    When you first go to the site you're greeted with a number of highly rated color schemes that use five colors, like Cold Lake, shown below.





    After browsing through a few themes, I found The Sky Through Glass, some of the colors of which you'll see on this blog. Usually I will find a scheme that I like and choose one or two of those colors for a web site. If you want the hex color codes, click on the somewhat hidden button outlined in red below to go to the editor.





    At the editor, you can find the color codes for any of the colors shown. You can also tweak existing themes to meet your needs or even create your own color schemes from scratch!





    I'd be really interested in seeing what kind of themes Neoseekers are using, so I'd appreciate if you leave a comment here if you're using a theme on your blog that you found or created on Kuler.

    Happy Coloring!

    Kuler

    color scheme blog themes forum themes web development technology

    Read more

    If you've been to Loungin' since July, you may have noticed the drop-down list in the forum header that allows you to change how you and only you view the forum. This gives seekers a great opportunity to view forums in a different light without annoying other people with their choice. The best part is that your choice is saved for the next time you view the forum, assuming you have cookies enabled.

    Since the feature hit Loungin' I've had a few PMs from moderators who want to do the same thing in their forum(s), but I haven't had a good answer for them...until now! I intend for this post to walk you through each step required to add this feature.

    Step 1. Build Your Themes

    Obviously in order to have a theme selector, you have to have themes! tekmosis gives a good introduction to theme making here, but there is one thing you have to keep in mind. Instead of creating your theme like this—

    code
    <style type="text/css">
    /* YOUR THEME DETAILS HERE */
    </style>
    


    You'll need to put your theme in an external .css file. For example, if this were what I used to put in the forum header—

    code
    <style type="text/css">
    body {
      background-color: black;
    }
    </style>
    


    Then all I would do is take the text between the style tags and put them in a file and save it as something like MyTheme.css. Remember, don't put the style tags in the file!

    Here are some examples from Loungin': Dark Knight, Aya Pink, Shane Blue


    Step 2. Upload Your Themes

    You'll next have to make sure that your themes are accessible online. If you don't have a way to host CSS files online, I'd recommend signing up for an account at Lycos; afterwards navigate to the WebFTP area and upload your files that way.


    Step 3. Upload the Script (Be Nice to Ren of Heavens)

    Ren is your friend when it comes to uploading scripts for your forum. If you ask him nicely to upload this script for your forum, you'll be one step closer to having your dynamic themes.


    Step 3b. Modify Your Forum Header (Optional)

    If you intend for the themes to change your forum header as well, you will need to ensure that your header has an ID attribute with the value "theme_header". For example—

    code
    <img src="http://i.neoseeker.com/m/31026_photo.gif" id="theme_header" />
    



    Step 4. Add the Theme Selector to Your Forum

    Let's review quickly. If you've reached this point, this means you have at least one custom theme you'd like to use, and you also have the script uploaded in your forum. Assuming that's the case, let's move on to the last part: the theme selector.

    The drop-down list is very easy to make; the template looks like this:

    code
    <select id="theme_selector">
      <option>Default</option>
      /* Additional Themes */
    </select>
    


    For each theme you want to add, follow this pattern:

    code
    <option value="THEME_KEY" csshref="CSS_URL" headersrc="FORUM_HEADER_URL">THEME_NAME</option>
    
    1. THEME_KEY: A one-character piece of text that identifies this theme. This means that each theme in your forum must have a unique THEME_KEY.
    2. CSS_URL: The URL for the CSS file you uploaded in Step 2 for this theme.
    3. FORUM_HEADER_URL: If you want to change the forum header to match your theme, put the URL for the new image here.
    4. THEME_NAME: This is what people will see in the theme selector, so make it a friendly name like "Aya Pink" or "Shane Blue".
    Here are some examples.

    Loungin'

    code
    <select id="theme_selector">
      <option>Default</option>
      <option csshref="http://neoloungin.makesgirlscrazy.com/AyaFix.css" value="a">Aya Pink</option>
      <option csshref="http://neoloungin.makesgirlscrazy.com/EnigmaS.css" value="e">Enigma Maroon</option>
      <option csshref="http://neoloungin.makesgirlscrazy.com/KjgFix.css" value="k">Kjg Blue</option>
    
      <option csshref="http://neoloungin.makesgirlscrazy.com/QuiertaS.css" value="q">Quierta Purple</option>
      <option csshref="http://neoloungin.makesgirlscrazy.com/ShaneS.css" value="s">Shane Blue</option>
      <option csshref="http://neoloungin.makesgirlscrazy.com/Black.css" value="b">Gun Metal</option>
      <option csshref="http://neoloungin.makesgirlscrazy.com/Poison Ivy.css" value="g">Poison Ivy</option>
      <option csshref="http://neoloungin.makesgirlscrazy.com/Sour Line.css" value="l">Sour Lime</option>
      <option csshref="http://neoloungin.makesgirlscrazy.com/DarkKnight2.css" value="d">Dark Knight</option>
    </select>
    


    HTML/CSS & NeoHome Help

    code
    <select id="theme_selector">
      <option>Neoseeker Theme (Default)</option>
      <option value="d" csshref="http://members.lycos.co.uk/suffusion/dynamic_themes/devpenTheme.css">DEVPEN Theme</option>
      <option value="t" csshref="http://members.lycos.co.uk/suffusion/dynamic_themes/tornsTheme.css" headersrc="http://members.lycos.co.uk/suffusion/dynamic_themes/torn_htmlforumbanner.jpg">Torn's Theme</option>
    </select>
    


    Afterwards, the theme selector and the script should work together to give you your custom themes.




    I hope this helps! Feel free to post any questions or comments you have here.

    neoseeker forum themes theme switching web development technology neoseeker related

    Read more

    Hi Neoseeker, I'm looking forward to blogging about my experiences with web development and NAMFox. At least, that's the plan; I hope I don't become too busy...

    But you might find things here about designing themes for forums, various HTML/CSS tips and tricks, and some information about NAMFox (both technical and non-technical) that might not appear in the NAM forum.

    My first post will probably be an FAQ about how to use the theme switcher in Loungin' in your own forums, since a lot of mods seem to want one. Stay tuned...

    namfox introduction purpose technology neoseeker related

    For those of you who talk to me on MSN or Google Talk, you may notice that I keep a word of the day in my personal message. Some ask where the word of the day comes from. The truth is that every day's word comes from my playing around on Free Rice. If you haven't heard of it, I would recommend checking it out. I treat it as a great way to learn vocabulary while doing some good for the world.

    I'm just a little bit proud of myself that I've been able to contribute 100,000 grains of rice on the site:



    Here's to another 100K!

    P.S. Today's word of the day is acanthoid; it means spiny. :)

    musingsthoughts free rice

    Most of what I know about cooking I learned by watching others prepare food and talking to them about their techniques. The problem with doing this is that when you want to recreate their dishes, you have to stitch together your memories of what you saw and what they said and pray that you end up with something that looks (and tastes!) halfway decent. So, in an endeavor to solve this problem, I've decided to write a blog post about each recipe that I see so I no longer have to keep these tucked away in my memory. As an added bonus, I can share the recipes with you guys and get some feedback about things I can do differently in future. So let's get started!



    The first dish that my mom and I prepared tonight was rainbow trout. It was a pretty straightforward dish, but sometimes it can be a little intimidating to look at fish and think "what the hell do I do with this?".

    Recipe

    Ingredients

    2 rainbow trout, organs removed (ours were about a foot long)
    Olive oil
    Small amounts of butter (1 oz. maximum)
    1 shallot
    White wine
    Salt and pepper

    Steps

    1. The first thing to prep the fish is to scale it if the fish you buy still has its scales. To do this you can take a knife and start scraping from the caudal fin (i.e. the tail) and make your way to the head. Keeping the knife at a 45 degree angle (being careful not to slice into the fish) also helps scale the fish more quickly. We did this in the sink, so it was easy to keep water running to help remove all the scales that build up on the knife.
    2. Afterwards, you'll need to wash the inside and outside of the trout (shown below). While you're working on the trout, add some olive oil and butter to a pan under low heat and preheat the oven to 400 degrees.
    3. You'll now have a wet fish :), so take a paper towel and dry the fish as much as you can.
    Now we turned the stove up to medium heat and seared the trout in the pan. Although we had good intentions (reduce the overall cooking time), we shouldn't have done this because it ended up ripping the skin from the fish (see below). We cut our losses and moved the fish to a casserole dish before seasoning it with a bit of salt and pepper.


    3. With the fish set aside we chopped up a shallot and threw it in the pan and waited for it to caramelize. I like shallots more than onions because the flavor they produce is not as intense as onions', so it doesn't overpower the main course.
    4. When the shallots are done, we poured the remaining mixture from the pan over the trout.
    5. The last step was to flesh out the sauce with some wine. We used a small amount of white wine and let that simmer for a bit before pouring that on the trout in the casserole dish.
    We stuck it in the oven for 20 minutes and then had a delicious meal with it. I enjoyed the flavors, but they were a little plain, and the fish still had tons of bones in it. The presentation also could be a little better next time (don't rip the skin off).



    There were actually more trout in the container than what we cooked tonight, so went through the first couple of steps of scaling, cleaning, and drying the fish before wrapping them in Saran wrap and aluminum foil to freeze. Then I suspect it will be simple enough to thaw them and try this again, perhaps with some different seasoning this time. Bon appetit!

    other food food recipes recipes fish fish

    Read more
    (1.5705/d/web6)