http://wiki.dreamwidth.net/wiki/api.php?action=feedcontributions&user=Exor674&feedformat=atomDreamwidth Notes - User contributions [en]2024-03-28T17:40:25ZUser contributionsMediaWiki 1.23.0//wiki.dreamwidth.net/wiki/index.php/Book_of_Wholesome_HobbiesBook of Wholesome Hobbies2015-06-27T06:20:23Z<p>Exor674: </p>
<hr />
<div>The Book of Wholesome Hobbies That Denise And Mark Have Forbidden Their Staff and Volunteers From Taking Part In<br />
<br />
[http://azurelunatic.dreamwidth.org/6590883.html Originates from IRC.] (This is [http://www.jargon.net/jargonfile/h/hahaonlyserious.html ha-ha-only-serious].) <br />
<br />
=Book of Wholesome Hobbies that are Forbidden=<br />
<br />
* Getting hit by a bus (essential or non-essential)<br />
* Deleting every single comment that an ex-friend has left in your journal and marking it as spam. (It's the marking it as spam part that's forbidden. Delete to your heart's content.)<br />
* Feeding the trolls.<br />
* Hanging out with that guy who points AK-47s at you.<br />
* Making a large number of completely outlandish suggestions in <dwcomm>dw_suggestions</dwcomm> for the sole purpose of making the person handling the moderation queue crack up.<br />
* Tossing x-acto knife like it's a pen while bored waiting for things to run http://qdb.dreamwidth.net/dw/258<br />
* Fall over and injure themselves<br />
* Stuff parmesan cheese up their noses<br />
* Turn Random Word hEll into a mutant supervillian (via gamma ray exposure, or any other method)<br />
* Summon Rah like an Elder God: http://qdb.dreamwidth.net/dw/488 and http://qdb.dreamwidth.net/dw/590<br />
* Buying chemical raw materials.<br />
* Playing with chemicals (unless that is part of your Actual Job Description at your Actual Job). http://qdb.dreamwidth.net/dw/516<br />
* Getting gum stuck in your nose hair. http://azurelunatic.dreamwidth.org/6723816.html<br />
* Engaging in knife fights to determine who gets to keep their confusingly similar IRC nick and who must change<br />
* Any sort of knife fight at all<br />
* [http://qdb.dreamwidth.net/dw/547 We probably have a rule about committing while on drugs.]<br />
* Doing a burn test on unknown or possibly wool fibers with inadequate ventilation http://qdb.dreamwidth.net/dw/225<br />
* Setting users on fire http://qdb.dreamwidth.net/dw/588<br />
* Attempting to ride a Segway into the library http://misskat.dreamwidth.org/3452087.html<br />
* Throwing things off the hotel balcony<br />
** In this case, people count as things.<br />
* Buying unwise sparklers<br />
* Spending excessive amounts of money on nail polish<br />
* Stay up past bedtime<br />
** Especially when "staying up past bedtime" will constitute going against a direct order from The Bossen<br />
* Lick the keyboard (even if it is flavored)<br />
** Questioning the need for the no-licking rule may result fairly rapidly in a lentil-flavored Bluetooth keyboard.<br />
* Leaving the balcony in a downward direction<br />
* Absolutely not allowed to take a personal day to kill or otherwise physically harm people who have annoyed you.<br />
** Not on company time either<br />
* Lick cacti<br />
* Eat your co-workers for dinner<br />
** or any other meal<br />
* Setting things (unspecified) on fire<br />
* Taking over any of the organizations on the blacklist, even if they could really use the upgrade in management<br />
* Summoning elder gods into the hotel we are staying in (our damage deposit doesn't cover it)<br />
** Or the hotels of beloved contributors.<br />
** Establishments with particularly good food should also be spared from the wrath of the sorts of elder gods who have been awakened from their slumber in the depths and desperately need a good cuppa.<br />
* Stuffing grape-flavored sprinkles up your boyfriend's nose.<br />
<br />
= Book of Unwholesome Hobbies that are Permitted = <br />
<br />
* [http://dw-dev.dreamwidth.org/86193.html?thread=699057#cmt699057 Code tours that make unwary readers spray drinks through their noses]. <br />
* Going to the conference without pants <br />
* Having ice cream for breakfast<br />
* Answering Support requests while intoxicated<br />
** If you're lucky, D will top off your drink for you so you don't have to get up.<br />
*** But approving your own answers while more intoxicated than usual is frowned on. <br />
* Setting voodoo dolls of appropriate accuracy on fire, outside, in approved fire locations, in accordance with state and local regulations, as long as it's not filmed for the internet.<br />
<br />
[[Category:Jargon]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Things_Real_Dreamwidth_Programmers_DoThings Real Dreamwidth Programmers Do2015-06-12T15:37:09Z<p>Exor674: /* Make Mistakes */</p>
<hr />
<div><br />
Wading into an open source project for the first time can be intimidating. There's a tendency to put established open source programmers up on a pedestal, especially when evaluating one's own abilities in comparison. (Hello, [http://geekfeminism.wikia.com/wiki/Impostor_syndrome impostor syndrome]!) <br />
<br />
Real Open Source Programmers are the ''crème de la crème'', the best of the best, the veritable titans of the programming world, right? They never make mistakes; they write flawless SQL queries in their sleep; they instantaneously comprehend any and all code they survey. Surely no mere mortal could ever hope to enter their exalted domain.<br />
<br />
Reality, of course, is far, far removed from this caricature. Real Open Source Programmers are humans like the rest of us, with the same foibles, insecurities, and quirks common to all. Very few contributors have supernatural abilities, decades of programming experience, or an encyclopedic knowledge of computing arcana, and we are all far, far from perfect. =)<br />
<br />
Thus, when <dwuser>azurelunatic</dwuser> shared the IRC logs of a marvellous [http://azurelunatic.dreamwidth.org/6731755.html Dreamwidth Dev Pep Talk], <dwuser>jeshyr</dwuser> [http://azurelunatic.dreamwidth.org/6731755.html?thread=12381163#cmt12381163 came up with the idea] of collecting an Epic List of Things Real Dreamwidth Programmers do. If you're a new or new-to-DW contributor, hopefully the list below will help disabuse you of any notion that you're somehow "not good enough" to contribute code -- '''you are more than good enough, and your contributions are heartily welcomed'''. And if you program, design, sysadmin, or interact with computers in any way, feel free to add any anecdotes you might have -- either signing your name or not. <br />
<br />
With that, we present the '''Epic List of Things Real Dreamwidth Programmers Do'''.<br />
<br />
<br />
== Ask for Help ==<br />
* I cannot count the number of times I asked someone who knew more about the documentation system how to do something, or to double-check to make sure I got it right. (This, after being a documentation admin for a while on LiveJournal.) - <dwuser>azurelunatic</dwuser><br />
* It's the rare code tour that I can complete without at least once asking what some bug was about. That's some deep, deep magic. - <dwuser>azurelunatic</dwuser><br />
* I'd like to patch this bug! Or try to! Really, I would! But... ''where do I find the existing code for it''??? ;_; - <dwuser>kaberett</dwuser> (Thanks to <dwuser>exor674</dwuser>'s subsequent work and <dwuser>foxfirefey</dwuser>'s documentation, probably [[Routing_and_Template_Cookbook:_Find_the_handler_for_a_URL|the handler finder tool]].)<br />
* I legit can't answer certain Support reqs (*coughRENAMEScough*) without looking up the doc or referring back to my notes. I've only been doing this for five years... -<dwuser>misskat</dwuser><br />
<br />
== Make Mistakes ==<br />
* Forgot that you can't treat a null as a zero enough times that I printed out "Null To Zero" in a fancy font with an ornate border, and stapled it to my wall as a reminder. - <dwuser>azurelunatic</dwuser><br />
* Started the arguments of a mailto: link in a wiki with an ampersand instead of a question mark, got garbage results after the address when testing it, blamed the (touchy, obscure, belligerent) mail application to its developers, and was schooled on my mailto: syntax in front of an audience of about 1,300. - <dwuser>azurelunatic</dwuser><br />
* For bug [http://bugs.dwscoalition.org/show_bug.cgi?id=4282 4282], I misunderstood what the request in bugzilla was asking and what the current behaviour was, and so my patch didn't fix what it was meant to fix AND it didn't even actually fix what I thought it was meant to fix! So it was doubly broken ... and then I got sick and had to unassign the bug, so I never got it fixed. - <dwuser>jeshyr</dwuser><br />
* Stare at a typo for over an hour before noticing it - <dwuser>deborah</dwuser> ([http://qdb.dreamwidth.net/dw/363 from qdb])<br />
* ... because you took a huge dose of meds and decided this was the best state to code in. - <dwuser>kaberett</dwuser><br />
* 1. Spend several hours beating my head against why my dreamhack won't load. 2. In desperation, ask other people to test loading my 'hack versus loading theirs. 3. In even more desperation, go to restart Apache. <code>stop-apache</code>!, I instruct my 'hack. <code> ... what?</code> replied my 'hack. <code>I--I'm not doing that thing. So I can't. Are you sure that's what you meant?</code> - <dwuser>kaberett</dwuser><br />
* "hrmmm. woah jesus. woah. [...] no,it's my bad :-p [...] I put a hook in that code to print out if someone gets a 403 by that codepath, to try to validate that assertion. But I put the hook on the wrong side of the if statement, so it was printing 'denying request from uniq' for everybody who was NOT getting a 403, which is a lot of requests. But I didn't realise that, and thought we were 403ing a million people. NEVER CHANGE, SELF" - <dwuser>mark</dwuser>, in [[IRC]]<br />
* Spend days beating your head against a problem caused by a single stray <code>&gt;</code>... ( I had <code>my $foo =&gt; $bar;</code> where I meant <code>my $foo = $bar;</code> -<dwuser>exor674</dwuser><br />
* Wonder why a regular expression isn't working, and finally realize you are accidentally inserting a process ID into the regex. -<dwuser>exor674</dwuser><br />
<br />
== Forget How Things Work (and just forget things) == <br />
<br />
* I feel like I end up looking up most Perl functions with <tt>perldoc -f ''function_name''</tt> every time I use them. Especially <tt>open</tt> and <tt>split</tt>, for some reason. O_o - <dwuser>shadowspar</dwuser><br />
* Have to look at the Template Toolkit documentation every time I have to do anything -<dwuser>exor674</dwuser> ( I should note that I am in charge of the BML to TT conversion )<br />
* About half the time, the morning after I write something I have to figure out what I did and how the hell it works ... it never stays in my brain! - <dwuser>jeshyr</dwuser><br />
* I try to keep track of what features have actually been implemented, and what ones are still waiting, but I forget all the time. Then these get mixed in with things that LiveJournal has developed since the code fork, and again mixed with things that the shared codebase used to do, but doesn't anymore since it was ripped out by the bytes by whichever dev had the code-machete that week. - <dwuser>azurelunatic</dwuser><br />
* The best thing is when I think "Oh, we should totally do this thing," and then one of several things happen. One, there's already a suggestion for it. Two, the suggestion's already been migrated to Bugzilla. Three, <em>I was the one who suggested it</em>. Four, when it's actually already been implemented. Five, when all of the above is true -- and <dwuser>denise</dwuser> lets the post to <dwcomm>dw_suggestions</dwcomm> through anyway, because she forgot too. I have lost count of how many times that's happened. - <dwuser>azurelunatic</dwuser><br />
* I work with R scripts in my "day job", and I keep trying to write Perl in R, and R in Perl. Neither works too well. - <dwuser>swaldman</dwuser><br />
* I filed a bug at work today. This is how that went: First, I noticed a little glitch with the Move Message dialog, which had been bugging me subconsciously for weeks, and just then floated to the top. I thought maybe I should file a help ticket, but thought that I'd filed enough help tickets for a while and this was minor, so I might as well save them the trouble and look for duplicates first. So I went to that product's bug tracker and searched for the most obvious keyword, which had 400 results. So I added the next most obvious keyword, which narrowed it down to about 90. Then I went down the list and started opening things in tabs, including two that by their titles looked like they might be duplicates of each other, but were entirely unrelated to what I was complaining about. After that, I read through the tabs I'd just opened, and found nothing that looked like the bug I was encountering. Then I looked at the two that might have been duplicates of each other, and found that they were in fact nothing at all alike except in the title. Then I looked at some of the other bugs that could have been duplicates of those, but those weren't very interesting. Reading through all those bugs had given me another couple useful keywords to try, so I tried that and got only about 10. Those were really quick to read through, so I did. And I closed the last tab from the bug tracker. Then I closed the help page, because I couldn't remember why I'd opened it, and obviously it was not for any good reason if I couldn't remember. Then I went back to my email and noticed that for some reason I'd stopped in the middle of moving a message. "Huh, that's weird," I thought. I looked briefly at the message to figure out where I wanted to move it. I looked at the dialog, which had been recently changed to retain the last folder that I'd moved something to, in case I was doing it a lot. I noticed that I couldn't see the highlight, and couldn't remember what it was. "That's obnoxious, I should file a bug," I said; "I can never remember anything like that longer than 30 seconds." I opened up a tab for a helpdesk ticket, and then realized what I'd just done. Yesterday I'd described my workflows to a dev as "Imagine severe ADHD, and people banging pots and pans in the background", and I stand by that description. - <dwuser>azurelunatic</dwuser><br />
* I know what I'm doing unless it means that forgetting the obvious will cause me to waste a few hours in which case I am guaranteed to forget the obvious >:| - <dwuser>mark</dwuser> (from http://qdb.dreamwidth.net/dw/602)<br />
* "my only weakness while adminning and drinking is bind. I can do anything else, and I can edit zonefiles without messing them up, but i can NEVER remember to increment the serial number." - <dwuser>dive</dwuser>(from http://qdb.dreamwidth.net/dw/430)<br />
* Note to self: support notification messages arrive a lot faster if there is a <tt>support-notify</tt> worker running. ^_^; <dwuser>shadowspar</dwuser><br />
* One time it took the customer, me, and second-tier support staring at the bounceback message and the listing of email addresses the customer had defined, and the help of a monospaced text-editor, to notice that someone (probably the customer) had transposed two vowels in their multi-vowel surname when setting up the email address. - <dwuser>azurelunatic</dwuser><br />
<br />
== Forget Their Password ==<br />
* And their usernames. And other essential details.<br />
* I not only forgot my Github password, I forgot the email address I'd registered with them! I had to email support (thankfully they were lovely about it). I also regularly forget to put the dh- in front of my Dreamhack username! -<dwuser>randomling</dwuser><br />
* This evening, I had to search email for how to log into my dreamhack, I had to try three passwords before I got the right one. Then I stared blankly at the Github password prompt and took some time to remember whether I even had a Github account. -<dwuser>kaberett</dwuser><br />
<br />
== Break Production (the Live Website) == <br />
<br />
* Kicked over production Apache at work the other day when I thought I was merely recycling the dev website. oops... - <dwuser>shadowspar</dwuser><br />
* Break the site (momentarily) during open beta launch: http://qdb.dreamwidth.net/dw/123<br />
* I was trying to update some software on one of our production databases and wasn't paying very close attention. I typed 'yes' when prompted "Really uninstall mysql-server?" I quickly failed over to the other database and reinstalled it, but... oy. <dwuser>mark</dwuser><br />
* Honestly, the ways in which I've abused production due to fat-fingering a command or thinking "I'll just test this live real fast"... <dwuser>mark</dwuser><br />
* Forget to restart memcached after a database crash, buy new servers because the site was slow (hey! we got new servers!)<br />
* But on the plus side, the bug I got committed to production only affected support volunteers with privs... --<dwuser>kaberett</dwuser><br />
<br />
== Break Sundry Other Things ==<br />
<br />
* My first act at one of my early contracting gigs: creating a mail routing loop that made our own servers bury themselves under a deluge of junk email - <dwuser>shadowspar</dwuser><br />
* Break things in their development environments. <br />
* Break the development environments (and associated infrastructure) themselves. <br />
* Back in the days (!!) of Mercurial Queues, I managed to mess up deleting patches that had been committed from my Dreamhack so badly that it took a lot of WTFing and a goodly amount of new documentation to sort it out without losing all my part-finished patches - <dwuser>kaberett</dwuser><br />
<br />
== Shoot Themselves in the Foot == <br />
<br />
* Starting to rewrite a helper script. Think "What version of Perl does the DW codebase require?" Ah! Find it [http://dw-dev.dreamwidth.org/120658.html requires 5.10]. Great, I can use the features that are new in 5.10! Write up a [http://wiki.dwscoalition.org/wiki/index.php/Suggested_Server_Requirements#Perl_5.10 note in the wiki] saying that Perl 5.10 is required, but it's unlikely to be a problem because any machine running 5.8 or earlier is likely to be quite decrepit indeed. Return to script, finish it, give it a try. Script refuses to run. Error message: <tt>'''Perl v5.10.0 required--this is only v5.8.9.'''</tt> <dwuser>shadowspar</dwuser><br />
* I just didn't have time to figure out a real, proper way to solve the problem, so I did something nasty and clunky that happened to work - dive-o ([http://qdb.dreamwidth.net/dw/432 from qdb])<br />
<br />
== Overcommit Themselves ==<br />
<br />
* Time elapsed between me showing up on DW and saying I wanted to contribute code, and actually submitting my first patch: about three years. ^_^; - <dwuser>shadowspar</dwuser><br />
* Assigning bugs to oneself in a fit of hopefulness, realise you can't manage them and sadly unassigning. Rinse, repeat :) - <dwuser>jeshyr</dwuser><br />
* Pick up an effort-minor bug, stare at it for a while, start hashing out a spec in bugzilla comments, and before you know it end up with an entire <dwuser>dw_dev</dwuser> discussion post about what you're ''actually'' trying to achieve, with reference to several ISO standards... and then bury your head in the sand and pretend none of it ever happened. - <dwuser>kaberett</dwuser><br />
<br />
== Get Fed Up ==<br />
<br />
* Today in IRC, <dwuser>denise</dwuser> was letting <dwuser>fu</dwuser> know about her recent pull request submitting a patch for [http://bugs.dwscoalition.org/show_bug.cgi?id=1386 bug 1386] with these words: "i gave you a PR for the revert-the-color-changes but my dw-nonfree branch is f***ed somehow so i probably screwed it up. can you take pity on me and just redo the changes on your end to commit them? [...] i'm probably going to have to do something awful to the damn thing to make it work again". I find it very reassuring that even our admins want to throw their computers out the window sometimes! - <dwuser>jeshyr</dwuser><br />
* I inherited a series of Excel spreadsheets trying to do a database's work, and by the end of my first time soloing, I threw a kicking, screaming tantrum & swore I'd never touch it again. My reward for this was to then build a database (with a grand total of 3 intro to db classes under my belt, plus the Microsoft help system) in Access, which meant untangling my predecessor's formulae, discovering her possibly civilly liable shoddy math, teaching myself more than I ever wanted to know about databases -- and ultimately never fully finishing it, as the company shut down my location. It was still better than dealing with that goddamn thing. - <dwuser>azurelunatic</dwuser><br />
<br />
== Get Scared ==<br />
<br />
* I ask for reassurance ALL the time, usually via the IRC channel. Hell, I even asked for reassurance before creating this category in this document ... how silly is that?? - <dwuser>jeshyr</dwuser><br />
* I spend a good week asking people to hold my hands and cheerlead me before every time I get going with a patch. Hell, I'm so terrified of git that I've not submitted a single patch since we started the migration - it took me months to work up the courage to migrate my [[Dreamhack]], never mind actually ''use'' git! - <dwuser>kaberett</dwuser><br />
* I have had a dreamhack since early days of the site, sometime in 2009 maybe. My first submitted patch was 2015, and done entirely via the github web interface. <em>I still have not logged in to my dreamhack.</em> - <dwuser>azurelunatic</dwuser><br />
<br />
== Complain ==<br />
<br />
* A ''lot''<br />
* In IRC<br />
* Out loud when the code is not making sense (as some sections of it frequently fail to do)<br />
* Including wondering what elder god infested Brad's head when he wrote ''THAT'' omg<br />
* At code they wrote themselves just six months ago but never got around to properly commenting<br />
* The good part to swearing in Chicken at work is that nobody's going to understand the words, just the tone of voice. The bad part is, now I am the lunatic making chicken noises at work. - <dwuser>azurelunatic</dwuser><br />
<br />
== Help Others == <br />
<br />
* I'm kind of thrilled when someone asks for help with the code tour and I can actually sort-of explain what some of the fixes do. ^_^; - <dwuser>shadowspar</dwuser><br />
* Spend two straight days rummaging around with ''something'' or other, and in a fit of FOR CRYING OUT LOUD write up some documentation for the wiki so at least the next person doesn't have to reinvent the wheel - <dwuser>kaberett</dwuser><br />
* Spent seriously like a week trying to look up over and over all the commands in the wiki for upgrading code, updating the database, using the version control, etc. and finally decide it was easier to just write a huge enormous [http://dw-dev.dreamwidth.org/94822.html omnibus script] to remember them for me. The fact it helps other people is fun, too! - <dwuser>jeshyr</dwuser><br />
* Realise that someone really ''hates'' a particular task that you'd find at worst pleasantly soothing, and take it off their hands. - <dwuser>kaberett</dwuser><br />
<br />
== Learn == <br />
<br />
* Better than half of our contributors have never programmed in Perl before, never contributed to an Open Source project before, or never programmed before, at all, period. I think that's amazing. ([http://denise.dreamwidth.org/23600.html ref]) - <dwuser>shadowspar</dwuser><br />
* I constantly learn things from people. Frontend stuff is a mystery to me, so I am always learning when I read patches submitted by our styles people. "Oh, CSS can do that? Woah." I still don't understand most of it. - <dwuser>mark</dwuser><br />
<br />
== Get Invested ==<br />
<br />
* Can't face brushing my teeth? That's okay, I'll spec up a bugfix! - <dwuser>kaberett</dwuser></div>Exor674//wiki.dreamwidth.net/wiki/index.php/Things_Real_Dreamwidth_Programmers_DoThings Real Dreamwidth Programmers Do2015-06-12T15:36:07Z<p>Exor674: /* Make Mistakes */</p>
<hr />
<div><br />
Wading into an open source project for the first time can be intimidating. There's a tendency to put established open source programmers up on a pedestal, especially when evaluating one's own abilities in comparison. (Hello, [http://geekfeminism.wikia.com/wiki/Impostor_syndrome impostor syndrome]!) <br />
<br />
Real Open Source Programmers are the ''crème de la crème'', the best of the best, the veritable titans of the programming world, right? They never make mistakes; they write flawless SQL queries in their sleep; they instantaneously comprehend any and all code they survey. Surely no mere mortal could ever hope to enter their exalted domain.<br />
<br />
Reality, of course, is far, far removed from this caricature. Real Open Source Programmers are humans like the rest of us, with the same foibles, insecurities, and quirks common to all. Very few contributors have supernatural abilities, decades of programming experience, or an encyclopedic knowledge of computing arcana, and we are all far, far from perfect. =)<br />
<br />
Thus, when <dwuser>azurelunatic</dwuser> shared the IRC logs of a marvellous [http://azurelunatic.dreamwidth.org/6731755.html Dreamwidth Dev Pep Talk], <dwuser>jeshyr</dwuser> [http://azurelunatic.dreamwidth.org/6731755.html?thread=12381163#cmt12381163 came up with the idea] of collecting an Epic List of Things Real Dreamwidth Programmers do. If you're a new or new-to-DW contributor, hopefully the list below will help disabuse you of any notion that you're somehow "not good enough" to contribute code -- '''you are more than good enough, and your contributions are heartily welcomed'''. And if you program, design, sysadmin, or interact with computers in any way, feel free to add any anecdotes you might have -- either signing your name or not. <br />
<br />
With that, we present the '''Epic List of Things Real Dreamwidth Programmers Do'''.<br />
<br />
<br />
== Ask for Help ==<br />
* I cannot count the number of times I asked someone who knew more about the documentation system how to do something, or to double-check to make sure I got it right. (This, after being a documentation admin for a while on LiveJournal.) - <dwuser>azurelunatic</dwuser><br />
* It's the rare code tour that I can complete without at least once asking what some bug was about. That's some deep, deep magic. - <dwuser>azurelunatic</dwuser><br />
* I'd like to patch this bug! Or try to! Really, I would! But... ''where do I find the existing code for it''??? ;_; - <dwuser>kaberett</dwuser> (Thanks to <dwuser>exor674</dwuser>'s subsequent work and <dwuser>foxfirefey</dwuser>'s documentation, probably [[Routing_and_Template_Cookbook:_Find_the_handler_for_a_URL|the handler finder tool]].)<br />
* I legit can't answer certain Support reqs (*coughRENAMEScough*) without looking up the doc or referring back to my notes. I've only been doing this for five years... -<dwuser>misskat</dwuser><br />
<br />
== Make Mistakes ==<br />
* Forgot that you can't treat a null as a zero enough times that I printed out "Null To Zero" in a fancy font with an ornate border, and stapled it to my wall as a reminder. - <dwuser>azurelunatic</dwuser><br />
* Started the arguments of a mailto: link in a wiki with an ampersand instead of a question mark, got garbage results after the address when testing it, blamed the (touchy, obscure, belligerent) mail application to its developers, and was schooled on my mailto: syntax in front of an audience of about 1,300. - <dwuser>azurelunatic</dwuser><br />
* For bug [http://bugs.dwscoalition.org/show_bug.cgi?id=4282 4282], I misunderstood what the request in bugzilla was asking and what the current behaviour was, and so my patch didn't fix what it was meant to fix AND it didn't even actually fix what I thought it was meant to fix! So it was doubly broken ... and then I got sick and had to unassign the bug, so I never got it fixed. - <dwuser>jeshyr</dwuser><br />
* Stare at a typo for over an hour before noticing it - <dwuser>deborah</dwuser> ([http://qdb.dreamwidth.net/dw/363 from qdb])<br />
* ... because you took a huge dose of meds and decided this was the best state to code in. - <dwuser>kaberett</dwuser><br />
* 1. Spend several hours beating my head against why my dreamhack won't load. 2. In desperation, ask other people to test loading my 'hack versus loading theirs. 3. In even more desperation, go to restart Apache. <code>stop-apache</code>!, I instruct my 'hack. <code> ... what?</code> replied my 'hack. <code>I--I'm not doing that thing. So I can't. Are you sure that's what you meant?</code> - <dwuser>kaberett</dwuser><br />
* "hrmmm. woah jesus. woah. [...] no,it's my bad :-p [...] I put a hook in that code to print out if someone gets a 403 by that codepath, to try to validate that assertion. But I put the hook on the wrong side of the if statement, so it was printing 'denying request from uniq' for everybody who was NOT getting a 403, which is a lot of requests. But I didn't realise that, and thought we were 403ing a million people. NEVER CHANGE, SELF" - <dwuser>mark</dwuser>, in [[IRC]]<br />
* Spend days beating your head against a problem caused by a single stray <code>&gt;</code>... ( I had my <code>$foo =&gt; $bar;</code> where I meant my <code>$foo = $bar;</code> -<dwuser>exor674</dwuser><br />
* Wonder why a regular expression isn't working, and finally realize you are accidentally inserting a process ID into the regex. -<dwuser>exor674</dwuser><br />
<br />
== Forget How Things Work (and just forget things) == <br />
<br />
* I feel like I end up looking up most Perl functions with <tt>perldoc -f ''function_name''</tt> every time I use them. Especially <tt>open</tt> and <tt>split</tt>, for some reason. O_o - <dwuser>shadowspar</dwuser><br />
* Have to look at the Template Toolkit documentation every time I have to do anything -<dwuser>exor674</dwuser> ( I should note that I am in charge of the BML to TT conversion )<br />
* About half the time, the morning after I write something I have to figure out what I did and how the hell it works ... it never stays in my brain! - <dwuser>jeshyr</dwuser><br />
* I try to keep track of what features have actually been implemented, and what ones are still waiting, but I forget all the time. Then these get mixed in with things that LiveJournal has developed since the code fork, and again mixed with things that the shared codebase used to do, but doesn't anymore since it was ripped out by the bytes by whichever dev had the code-machete that week. - <dwuser>azurelunatic</dwuser><br />
* The best thing is when I think "Oh, we should totally do this thing," and then one of several things happen. One, there's already a suggestion for it. Two, the suggestion's already been migrated to Bugzilla. Three, <em>I was the one who suggested it</em>. Four, when it's actually already been implemented. Five, when all of the above is true -- and <dwuser>denise</dwuser> lets the post to <dwcomm>dw_suggestions</dwcomm> through anyway, because she forgot too. I have lost count of how many times that's happened. - <dwuser>azurelunatic</dwuser><br />
* I work with R scripts in my "day job", and I keep trying to write Perl in R, and R in Perl. Neither works too well. - <dwuser>swaldman</dwuser><br />
* I filed a bug at work today. This is how that went: First, I noticed a little glitch with the Move Message dialog, which had been bugging me subconsciously for weeks, and just then floated to the top. I thought maybe I should file a help ticket, but thought that I'd filed enough help tickets for a while and this was minor, so I might as well save them the trouble and look for duplicates first. So I went to that product's bug tracker and searched for the most obvious keyword, which had 400 results. So I added the next most obvious keyword, which narrowed it down to about 90. Then I went down the list and started opening things in tabs, including two that by their titles looked like they might be duplicates of each other, but were entirely unrelated to what I was complaining about. After that, I read through the tabs I'd just opened, and found nothing that looked like the bug I was encountering. Then I looked at the two that might have been duplicates of each other, and found that they were in fact nothing at all alike except in the title. Then I looked at some of the other bugs that could have been duplicates of those, but those weren't very interesting. Reading through all those bugs had given me another couple useful keywords to try, so I tried that and got only about 10. Those were really quick to read through, so I did. And I closed the last tab from the bug tracker. Then I closed the help page, because I couldn't remember why I'd opened it, and obviously it was not for any good reason if I couldn't remember. Then I went back to my email and noticed that for some reason I'd stopped in the middle of moving a message. "Huh, that's weird," I thought. I looked briefly at the message to figure out where I wanted to move it. I looked at the dialog, which had been recently changed to retain the last folder that I'd moved something to, in case I was doing it a lot. I noticed that I couldn't see the highlight, and couldn't remember what it was. "That's obnoxious, I should file a bug," I said; "I can never remember anything like that longer than 30 seconds." I opened up a tab for a helpdesk ticket, and then realized what I'd just done. Yesterday I'd described my workflows to a dev as "Imagine severe ADHD, and people banging pots and pans in the background", and I stand by that description. - <dwuser>azurelunatic</dwuser><br />
* I know what I'm doing unless it means that forgetting the obvious will cause me to waste a few hours in which case I am guaranteed to forget the obvious >:| - <dwuser>mark</dwuser> (from http://qdb.dreamwidth.net/dw/602)<br />
* "my only weakness while adminning and drinking is bind. I can do anything else, and I can edit zonefiles without messing them up, but i can NEVER remember to increment the serial number." - <dwuser>dive</dwuser>(from http://qdb.dreamwidth.net/dw/430)<br />
* Note to self: support notification messages arrive a lot faster if there is a <tt>support-notify</tt> worker running. ^_^; <dwuser>shadowspar</dwuser><br />
* One time it took the customer, me, and second-tier support staring at the bounceback message and the listing of email addresses the customer had defined, and the help of a monospaced text-editor, to notice that someone (probably the customer) had transposed two vowels in their multi-vowel surname when setting up the email address. - <dwuser>azurelunatic</dwuser><br />
<br />
== Forget Their Password ==<br />
* And their usernames. And other essential details.<br />
* I not only forgot my Github password, I forgot the email address I'd registered with them! I had to email support (thankfully they were lovely about it). I also regularly forget to put the dh- in front of my Dreamhack username! -<dwuser>randomling</dwuser><br />
* This evening, I had to search email for how to log into my dreamhack, I had to try three passwords before I got the right one. Then I stared blankly at the Github password prompt and took some time to remember whether I even had a Github account. -<dwuser>kaberett</dwuser><br />
<br />
== Break Production (the Live Website) == <br />
<br />
* Kicked over production Apache at work the other day when I thought I was merely recycling the dev website. oops... - <dwuser>shadowspar</dwuser><br />
* Break the site (momentarily) during open beta launch: http://qdb.dreamwidth.net/dw/123<br />
* I was trying to update some software on one of our production databases and wasn't paying very close attention. I typed 'yes' when prompted "Really uninstall mysql-server?" I quickly failed over to the other database and reinstalled it, but... oy. <dwuser>mark</dwuser><br />
* Honestly, the ways in which I've abused production due to fat-fingering a command or thinking "I'll just test this live real fast"... <dwuser>mark</dwuser><br />
* Forget to restart memcached after a database crash, buy new servers because the site was slow (hey! we got new servers!)<br />
* But on the plus side, the bug I got committed to production only affected support volunteers with privs... --<dwuser>kaberett</dwuser><br />
<br />
== Break Sundry Other Things ==<br />
<br />
* My first act at one of my early contracting gigs: creating a mail routing loop that made our own servers bury themselves under a deluge of junk email - <dwuser>shadowspar</dwuser><br />
* Break things in their development environments. <br />
* Break the development environments (and associated infrastructure) themselves. <br />
* Back in the days (!!) of Mercurial Queues, I managed to mess up deleting patches that had been committed from my Dreamhack so badly that it took a lot of WTFing and a goodly amount of new documentation to sort it out without losing all my part-finished patches - <dwuser>kaberett</dwuser><br />
<br />
== Shoot Themselves in the Foot == <br />
<br />
* Starting to rewrite a helper script. Think "What version of Perl does the DW codebase require?" Ah! Find it [http://dw-dev.dreamwidth.org/120658.html requires 5.10]. Great, I can use the features that are new in 5.10! Write up a [http://wiki.dwscoalition.org/wiki/index.php/Suggested_Server_Requirements#Perl_5.10 note in the wiki] saying that Perl 5.10 is required, but it's unlikely to be a problem because any machine running 5.8 or earlier is likely to be quite decrepit indeed. Return to script, finish it, give it a try. Script refuses to run. Error message: <tt>'''Perl v5.10.0 required--this is only v5.8.9.'''</tt> <dwuser>shadowspar</dwuser><br />
* I just didn't have time to figure out a real, proper way to solve the problem, so I did something nasty and clunky that happened to work - dive-o ([http://qdb.dreamwidth.net/dw/432 from qdb])<br />
<br />
== Overcommit Themselves ==<br />
<br />
* Time elapsed between me showing up on DW and saying I wanted to contribute code, and actually submitting my first patch: about three years. ^_^; - <dwuser>shadowspar</dwuser><br />
* Assigning bugs to oneself in a fit of hopefulness, realise you can't manage them and sadly unassigning. Rinse, repeat :) - <dwuser>jeshyr</dwuser><br />
* Pick up an effort-minor bug, stare at it for a while, start hashing out a spec in bugzilla comments, and before you know it end up with an entire <dwuser>dw_dev</dwuser> discussion post about what you're ''actually'' trying to achieve, with reference to several ISO standards... and then bury your head in the sand and pretend none of it ever happened. - <dwuser>kaberett</dwuser><br />
<br />
== Get Fed Up ==<br />
<br />
* Today in IRC, <dwuser>denise</dwuser> was letting <dwuser>fu</dwuser> know about her recent pull request submitting a patch for [http://bugs.dwscoalition.org/show_bug.cgi?id=1386 bug 1386] with these words: "i gave you a PR for the revert-the-color-changes but my dw-nonfree branch is f***ed somehow so i probably screwed it up. can you take pity on me and just redo the changes on your end to commit them? [...] i'm probably going to have to do something awful to the damn thing to make it work again". I find it very reassuring that even our admins want to throw their computers out the window sometimes! - <dwuser>jeshyr</dwuser><br />
* I inherited a series of Excel spreadsheets trying to do a database's work, and by the end of my first time soloing, I threw a kicking, screaming tantrum & swore I'd never touch it again. My reward for this was to then build a database (with a grand total of 3 intro to db classes under my belt, plus the Microsoft help system) in Access, which meant untangling my predecessor's formulae, discovering her possibly civilly liable shoddy math, teaching myself more than I ever wanted to know about databases -- and ultimately never fully finishing it, as the company shut down my location. It was still better than dealing with that goddamn thing. - <dwuser>azurelunatic</dwuser><br />
<br />
== Get Scared ==<br />
<br />
* I ask for reassurance ALL the time, usually via the IRC channel. Hell, I even asked for reassurance before creating this category in this document ... how silly is that?? - <dwuser>jeshyr</dwuser><br />
* I spend a good week asking people to hold my hands and cheerlead me before every time I get going with a patch. Hell, I'm so terrified of git that I've not submitted a single patch since we started the migration - it took me months to work up the courage to migrate my [[Dreamhack]], never mind actually ''use'' git! - <dwuser>kaberett</dwuser><br />
* I have had a dreamhack since early days of the site, sometime in 2009 maybe. My first submitted patch was 2015, and done entirely via the github web interface. <em>I still have not logged in to my dreamhack.</em> - <dwuser>azurelunatic</dwuser><br />
<br />
== Complain ==<br />
<br />
* A ''lot''<br />
* In IRC<br />
* Out loud when the code is not making sense (as some sections of it frequently fail to do)<br />
* Including wondering what elder god infested Brad's head when he wrote ''THAT'' omg<br />
* At code they wrote themselves just six months ago but never got around to properly commenting<br />
* The good part to swearing in Chicken at work is that nobody's going to understand the words, just the tone of voice. The bad part is, now I am the lunatic making chicken noises at work. - <dwuser>azurelunatic</dwuser><br />
<br />
== Help Others == <br />
<br />
* I'm kind of thrilled when someone asks for help with the code tour and I can actually sort-of explain what some of the fixes do. ^_^; - <dwuser>shadowspar</dwuser><br />
* Spend two straight days rummaging around with ''something'' or other, and in a fit of FOR CRYING OUT LOUD write up some documentation for the wiki so at least the next person doesn't have to reinvent the wheel - <dwuser>kaberett</dwuser><br />
* Spent seriously like a week trying to look up over and over all the commands in the wiki for upgrading code, updating the database, using the version control, etc. and finally decide it was easier to just write a huge enormous [http://dw-dev.dreamwidth.org/94822.html omnibus script] to remember them for me. The fact it helps other people is fun, too! - <dwuser>jeshyr</dwuser><br />
* Realise that someone really ''hates'' a particular task that you'd find at worst pleasantly soothing, and take it off their hands. - <dwuser>kaberett</dwuser><br />
<br />
== Learn == <br />
<br />
* Better than half of our contributors have never programmed in Perl before, never contributed to an Open Source project before, or never programmed before, at all, period. I think that's amazing. ([http://denise.dreamwidth.org/23600.html ref]) - <dwuser>shadowspar</dwuser><br />
* I constantly learn things from people. Frontend stuff is a mystery to me, so I am always learning when I read patches submitted by our styles people. "Oh, CSS can do that? Woah." I still don't understand most of it. - <dwuser>mark</dwuser><br />
<br />
== Get Invested ==<br />
<br />
* Can't face brushing my teeth? That's okay, I'll spec up a bugfix! - <dwuser>kaberett</dwuser></div>Exor674//wiki.dreamwidth.net/wiki/index.php/Dev_ToolsDev Tools2013-08-01T21:53:47Z<p>Exor674: </p>
<hr />
<div>{{Update|text=DW has moved to git so this is no longer accurate.}}<br />
<br />
<dwuser>exor674</dwuser> and <dwuser>afuna</dwuser> set up a [[mercurial]] repository for useful development tools. <br />
<br />
== Syncing with the tools repository ==<br />
<br />
First check the tools out into the ext directory:<br />
<br />
cd $LJHOME/ext<br />
hg clone https://bitbucket.org/anall/dwdev-tools/<br />
<br />
Then, you will either want to add the bin contents into your <code>$PATH</code>, or call them manually:<br />
<br />
$LJHOME/ext/dwdev-tools/bin/ljconsole<br />
<br />
You will have to manually update this directory:<br />
<br />
cd $LJHOME/ext/dwdev-tools<br />
hg pull<br />
hg update<br />
<br />
== Current Tools ==<br />
<br />
* [[Dev_Tools/s2sync]]<br />
* [[Dev_Tools/schwartz_buildup]]<br />
* [[Dev_Tools/ljconsole]]<br />
* [[Dev_Tools/inject-latest]]<br />
* [[Dev_Tools/memcache_view]]<br />
<br />
== Past Tools ==<br />
<br />
* lookup-routing: Now in the main repository: [[Routing and Template Cookbook: Find the handler for a URL]]<br />
<br />
[[Category: Development]]<br />
[[Category: Dev Tools]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Dev_ToolsDev Tools2013-08-01T21:53:35Z<p>Exor674: </p>
<hr />
<div>{{Update|text=DW has moved to git so this is no longer accurate.}}<br />
<br />
<dwuser>exor674</dwuser> and <dwuser>afuna</dwuser> set up a [[mercurial]] repository for useful development tools. <br />
<br />
== Syncing with the tools repository ==<br />
<br />
First check the tools out into the ext directory:<br />
<br />
cd $LJHOME/ext<br />
hg clone https://bitbucket.org/anall/dwdev-tools/<br />
<br />
Then, you will either want to add the bin contents into your <code>$PATH</code>, or call them manually:<br />
<br />
$LJHOME/ext/dwdev-tools/bin/ljconsole<br />
<br />
You will have to manually update this directory:<br />
<br />
cd $LJHOME/ext/dwdev-tools<br />
hg pull<br />
hg update<br />
<br />
== Current Tools ==<br />
<br />
* [[Dev_Tools/s2sync]]<br />
* [[Dev_Tools/schwartz_buildup]]<br />
* [[Dev_Tools/ljconsole]]<br />
* [[Dev_Tools/inject-latest]]<br />
* [[Dev_Tools/memcache_view]]<br />
<br />
== Past Tools<br />
<br />
* lookup-routing: Now in the main repository: [[Routing and Template Cookbook: Find the handler for a URL]]<br />
<br />
[[Category: Development]]<br />
[[Category: Dev Tools]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Dev_Tools/lookup-routingDev Tools/lookup-routing2013-08-01T21:52:30Z<p>Exor674: This tool's in the official repo now.</p>
<hr />
<div>#REDIRECT [[Routing and Template Cookbook: Find the handler for a URL]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Talk:Styles_and_Themes_ChecklistTalk:Styles and Themes Checklist2013-07-26T22:01:13Z<p>Exor674: Exor674 moved page Talk:Styles journal icons page to Talk:Styles and Themes Checklist</p>
<hr />
<div><br />
-- [[User:Ninetydegrees|Ninetydegrees]] ([[User talk:Ninetydegrees|talk]]) 10:32, 9 July 2013 (UTC)<br />
<br />
* Overall, pretty excellent! The one thing I would like (and am willing to try to compile) is to split off the list of stuff in 'check everything' to a more concrete list of all the major things to check - I know that would solve a lot of my 'oh, it turns out this layout breaks horribly if comments are disabled!' type bugs. --[[User:Momiji|Momiji]] ([[User talk:Momiji|talk]]) 08:37, 12 July 2013 (UTC)<br />
** Very good idea! Also two different lists (one for theme and one for styles) would be better I think.--[[User:Ninetydegrees|Ninetydegrees]] ([[User talk:Ninetydegrees|talk]]) 09:42, 12 July 2013 (UTC)<br />
**P.S. Actually maybe it would be useful to split this into two different articles even if we repeat some things. I'm thinking it might make it easier and less scary for newbies. What do you think?<br />
<br />
<br />
<br />
While patching themes you may also notice unrelated issues (text overlapping, element positioned incorrectly in certain circumstances, etc.) If this happens please mention it on the bug file or open a new bug so it can be fixed.<br />
--[[User:Ninetydegrees|Ninetydegrees]] ([[User talk:Ninetydegrees|talk]]) 10:44, 12 July 2013 (UTC)<br />
<br />
Before I forget: http://wiki.dwscoalition.org/wiki/index.php/Testing_layout_submissions --[[User:Ninetydegrees|Ninetydegrees]] ([[User talk:Ninetydegrees|talk]]) 12:50, 12 July 2013 (UTC)</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Styles_journal_icons_pageStyles journal icons page2013-07-26T22:01:13Z<p>Exor674: Exor674 moved page Styles journal icons page to Styles and Themes Checklist</p>
<hr />
<div>#REDIRECT [[Styles and Themes Checklist]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Talk:Styles_journal_icons_pageTalk:Styles journal icons page2013-07-26T22:01:13Z<p>Exor674: Exor674 moved page Talk:Styles journal icons page to Talk:Styles and Themes Checklist</p>
<hr />
<div>#REDIRECT [[Talk:Styles and Themes Checklist]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Styles_and_Themes_ChecklistStyles and Themes Checklist2013-07-26T22:01:13Z<p>Exor674: Exor674 moved page Styles journal icons page to Styles and Themes Checklist</p>
<hr />
<div><br />
Compiled from [[http://wiki.dwscoalition.org/wiki/index.php/Talk:Newbie_Guide:_How_to_Patch_Styles_and_Themes here]] and [[http://dreamscapes.dreamwidth.org/42341.html here]]<br />
= Patch Check List =<br />
<br />
==Dreamwidth Coding Standards (general)==<br />
<br />
* Usage rights (resource credits, credits in HTML or CSS comments, dw-free or nonfree)<br />
<br />
* No tabs<br />
<br />
* No trailing spaces<br />
<br />
==General testing things==<br />
[[http://wiki.dwscoalition.org/wiki/index.php/Testing_layout_submissions Review the guidelines for testing layout submissions]] as you are testing your style.<br />
<br />
==Styles (general):==<br />
<br />
* Always set layout_authors<br />
<br />
* Tabula Rasa child whenever possible<br />
<br />
* Supports all column modes whenever possible and at least one one-column<br />
<br />
* Supports all options from Tabula Rasa<br />
<br />
* Custom props sorted into existing propgroups using _child<br />
<br />
* Custom props also sorted into existing /customize cats in the style.pm file.<br />
<br />
* Custom props follow naming standards<br />
<br />
* Check everything, everywhere. In particular: <br />
** non-default column modes<br />
** modules in every available position<br />
** community accounts<br />
** unusually short or long elements (subjects, tags, titles, entries, comments, usernames...)<br />
** userpic stuff (no userpic, non-square userpics, smaller userpics)<br />
** collapsed comments<br />
** comments disabled<br />
** bottomcomment area<br />
** reply page,<br />
** quick reply<br />
**icon page<br />
** pagination links (reading, archive, comments and icon page)<br />
** contextual pop-ups<br />
** navigation strip<br />
** page text (sometimes only visible when there are no entries on the reading page).<br />
<br />
<br />
== Themes (general) ==<br />
<br />
* Alphabetized themes<br />
<br />
* Two blank lines between each theme<br />
<br />
* theme_authors if theme author is NOT style author. Otherwise no.<br />
<br />
* Props separated by headers (Presentation, Page Colors, Entry Colors, Module Colors, Fonts and Images, in this order. )<br />
<br />
* Alphabetized props within each cat.<br />
<br />
* No empty props<br />
<br />
<br />
==CSS (general)==<br />
<br />
* If TR child goes into function print_stylesheet () for styles. Otherwise goes into function Page::print_default_stylesheet.<br />
<br />
* Theme-specific CSS goes into function Page::print_theme_stylesheet()<br />
<br />
* No broad resets<br />
<br />
* Comment headers<br />
<br />
* Indents<br />
<br />
* Shorthand<br />
<br />
<br />
==Colors==<br />
<br />
* color_page_text in every theme<br />
<br />
* No half foreground/background combos (e.g. text color without background color)<br />
<br />
* Matching foreground/background combos (such as entry text color on entry background color)<br />
<br />
* Order in props for link colors: alphabetical so _link, _link_active, _link_hover, _link_visited<br />
<br />
* BUT order in CSS for link colors: hierarchical so a, a:visited, a:hover, a:active<br />
<br />
* Shorthand for color hex codes<br />
<br />
<br />
==Fonts==<br />
<br />
* Single quotes around font names with multiple words; no quotes otherwise.<br />
<br />
* No font_units settings if no font_size too.<br />
<br />
* No hardcoded font names<br />
<br />
* No hardcoded font sizes (some exceptions allowed) [what are they?]<br />
<br />
<br />
==Images==<br />
<br />
* No color profiles<br />
<br />
* Color-indexed PNGs whenever possible. JPGs with a lower res are sometimes a good alternative.<br />
<br />
* Common images in /commons<br />
<br />
* Follows file naming standards<br />
<br />
= Themes: Things to check =<br />
<br />
As themes mainly aim at setting colors, you mostly want to check that everything is visible (no white on white for example). Keep in mind that most designers test things on their own journals and therefore don't have things set up to check for every possible color issue. You'll want to focus on things which aren't standard in a journal or are easily overlooked.<br />
<br />
Tip: set up an account with dummy entries, dummy comments, etc. which cover as many different situations as possible. Once you've done that, checking a theme won't take you more than a few minutes. (TODO: mention https://gist.github.com/anall/6083205 and http://wiki.dwscoalition.org/wiki/index.php/Script:_create-users)<br />
<br />
* Credit is correct in /customize<br />
<br />
* Credit is correct in the journal credit module<br />
<br />
* Colors look all right on all pages: recent entries, reading page, year page, month page, day page, tags page, icons page, entry page w/ comments, (full and collapsed), entry page w/ quick reply, full reply page<br />
<br />
* Colors look all right in modules<br />
<br />
* Colors look all right in the navigation strip<br />
<br />
* Colors look all right in contextual pop-ups<br />
<br />
* Text and links to check in particular:<br />
<br />
**poster name area on Reading page<br />
**'There are no earlier entries to display' text on Reading page<br />
**'Top of Page' link<br />
**'no subject' text in entries and comments<br />
**bottomcomment area (text and links at the bottom of entry pages)<br />
**frozen and screened comments (this adds text before the subject link)<br />
**pagination links on entry pages<br />
**hover and visited on entry subjects, comment subjects, module links, calendars<br />
<br />
<br />
* You can also spot issues by looking at the theme code: if a text or a link color code is identical to its corresponding background one then it's very likely to be an issue and there should be some text you can't see on your test journal.<br />
<br />
<br />
* Issues: what then?<br />
<br />
If something looks completely unreadable to you, it can come from the theme or the style. If you know it comes from the theme kindly mention it to the designer on dreamscapes and ask them to tell you what the color shoudl be instead. If it comes from the style or you don't know what causes the issue mention it on the bug so someone can troubleshoot.<br />
<br />
=All Styles Should...=<br />
<br />
Dreamwidth makes great effort toward providing the same options in all of its styles but it can be hard for designers and developers to keep track of updates and integrate all options into the designs. I hope listing current required standards will make it easier for everybody.<br />
<br />
All styles should:<br />
<br />
* Have no hardcoded font faces. (bug #2629, IN PROGRESS) *<br />
<br />
* Have no hardcoded font sizes for major elements (see existing options). (no specific bug, IMPLEMENTED)<br />
<br />
* Have no hardcoded colors. (several bugs, IMPLEMENTED)<br />
<br />
* Have no CSS resets which would prevent the correct display of HTML elements users might include in their posts. (bug 4045, IMPLEMENTED)<br />
<br />
* Support the one-column display mode. (bug #1798, IN PROGRESS) *<br />
<br />
* Support left and right sidebar placement if a multi-column mode is available. (several bugs, bug #3185, IN PROGRESS) *<br />
<br />
* Support all Tabula Rasa presentation options. (several bugs, bug #3775, IN PROGRESS) *<br />
<br />
* Support all Tabula Rasa color options. (bug #3190, IN PROGRESS) *<br />
<br />
* Support all Tabula Rasa font options. (bug #2195, IMPLEMENTED)<br />
<br />
* Support all Tabula Rasa image options. (several bugs, bug #3052, IN PROGRESS) *<br />
<br />
* Support the placement of any module in all available positions or restrict the positions certain modules can be in. (several bugs, IMPLEMENTED)<br />
<br />
* Make sure header modules, if there are any, wrap correctly when screen size is reduced. (several bugs, IMPLEMENTED)<br />
<br />
* Have a 'Back to Top' link at the bottom of every page. (bug #3472, IN PROGRESS) *<br />
<br />
* Have at least one light on dark theme for each style. (unofficial, IMPLEMENTED)<br />
<br />
<br />
Items marked with an * indicate that this item is not true of all styles yet but style designers should keep this in mind for future styles.</div>Exor674//wiki.dreamwidth.net/wiki/index.php/XMLRPC_ProtocolXMLRPC Protocol2013-06-24T18:39:12Z<p>Exor674: Redirected page to XML-RPC Protocol</p>
<hr />
<div>#REDIRECT [[XML-RPC Protocol]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/XMLRPC_ProtocolXMLRPC Protocol2013-06-24T18:38:52Z<p>Exor674: Replaced content with "#REDIRECT XML-RPC Protocol"</p>
<hr />
<div>#REDIRECT [[XML-RPC Protocol]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Things_Real_Dreamwidth_Programmers_DoThings Real Dreamwidth Programmers Do2013-04-05T05:30:37Z<p>Exor674: /* Forget How Things Work */</p>
<hr />
<div><br />
Wading into an open source project for the first time can be intimidating. There's a tendency to put established open source programmers up on a pedestal, especially when evaluating one's own abilities in comparison. (Hello, [http://geekfeminism.wikia.com/wiki/Impostor_syndrome impostor syndrome]!) <br />
<br />
Real Open Source Programmers are the ''crème de la crème'', the best of the best, the veritable titans of the programming world, right? They never make mistakes; they write flawless SQL queries in their sleep; they instantaneously comprehend any and all code they survey. Surely no mere mortal could ever hope to enter their exalted domain.<br />
<br />
Reality, of course, is far, far removed from this caricature. Real Open Source Programmers are humans like the rest of us, with the same foibles, insecurities, and quirks common to all. Very few contributors have supernatural abilities, decades of programming experience, or an encyclopedic knowledge of computing arcana, and we are all far, far from perfect. =)<br />
<br />
Thus, <dwuser>jeshyr</dwuser> [http://azurelunatic.dreamwidth.org/6731755.html?thread=12381163#cmt12381163 came up with the idea] of collecting an Epic List of Things Real Dreamwidth Programmers do. If you're a new or new-to-DW contributor, hopefully the list below will help disabuse you of any notion that you're somehow "not good enough" to contribute code -- '''you are more than good enough, and your contributions are heartily welcomed'''. And if you program, design, sysadmin, or interact with computers in any way, feel free to add any anecdotes you might have -- either signing your name or not. <br />
<br />
With that, we present the '''Epic List of Things Real Dreamwidth Programmers Do'''.<br />
<br />
<br />
== Ask for Help ==<br />
<br />
== Make Mistakes ==<br />
* Forgot that you can't treat a null as a zero enough times that I printed out "Null To Zero" in a fancy font with an ornate border, and stapled it to my wall as a reminder. - <dwuser>azurelunatic</dwuser><br />
<br />
== Forget How Things Work == <br />
<br />
* I feel like I end up looking up most Perl functions with <tt>perldoc -f ''function_name''</tt> every time I use them. Especially <tt>open</tt> and <tt>split</tt>, for some reason. O_o - <dwuser>shadowspar</dwuser><br />
* Have to look at the Template Toolkit documentation every time I have to do anything -<dwuser>exor674</dwuser> ( I should note that I am in charge of the BML to TT conversion )<br />
<br />
== Break Production (the Live Website) == <br />
<br />
* kicked over production Apache at work the other day when I thought I was merely recycling the dev website. oops... - <dwuser>shadowspar</dwuser><br />
<br />
== Break Sundry Other Things ==<br />
<br />
* my first act at one of my early contracting gigs: creating a mail routing loop that made our own servers bury themselves under a deluge of junk email - <dwuser>shadowspar</dwuser><br />
<br />
== Overcommit themselves ==<br />
<br />
* time elapsed between me showing up on DW and saying I wanted to contribute code, and actually submitting my first patch: about three years. ^_^; - <dwuser>shadowspar</dwuser></div>Exor674//wiki.dreamwidth.net/wiki/index.php/Code_ReviewCode Review2013-03-16T12:00:47Z<p>Exor674: </p>
<hr />
<div><br />
<div class="warnbox"><b>Warning</b>: The following information is obsolete, and may quite possibly be incorrect. Obsolete articles are candidates for deletion. Information posted to official communities should be assumed to be accurate. If you have fresh information, please update this article (and remove this textbox!)<br><br><font size="-1"><center>This box was added on {{CURRENTMONTHNAME}} {{CURRENTDAY}}, {{CURRENTYEAR}}.</center></font></div>[[Category: Obsolete]]<br />
<br />
{{Note|text=This page has not been updated for the git conversion.}}<br />
<br />
Code Review is an important task that must be done for patches submitted to [[Bugzilla]]. By doing this task, you help <dwuser>mark</dwuser> and <dwuser>fu</dwuser> spend less time doing code review and more time coding.<br />
<br />
Here are [http://bugs.dwscoalition.org/buglist.cgi?cmdtype=dorem&remaction=run&namedcmd=needs-review&sharer_id=2 bugs in need of review].<br />
<br />
== Code Review Steps ==<br />
<br />
=== Style compliance ===<br />
<br />
Run through the [[Programming Guideline Checklist]]. If the patch violates any of these guidelines, reject with specific instructions on what is wrong.<br />
<br />
=== Build a test-case ===<br />
<br />
Look at what the patch is supposed to fix and reproduce it on your own installation. Already at this step it is best to think of several ways this patch can be tested so you can see what happens before and after you apply the patch.<br />
<br />
=== Apply the Patch ===<br />
<br />
Update to the latest committed code, as per the instructions in [[Dev Maintenance]]. Apply the patch from the pull request. If the patch doesn't apply cleanly, reject with a note to check their patch against the latest committed code. They may have forgotten to update before submitting, or there may have been changes committed since they submitted!<br />
<br />
=== Test the functionality ===<br />
<br />
Note what the bug patch is meant to fix. Verify that the issue is fixed or that the new feature is working properly. Try to see if you can make things break. <br />
<br />
Suggestions for things to test for, as applicable: test with paid, free, personal and community accounts, logged in and logged out, use different site schemes and layouts, try foreign characters in user input or empty input, entries and user names that don't exist, and anything else you can think of.<br />
<br />
=== Make appropriate changes to the review tag ===<br />
<br />
* If the patch follows style guidelines, applies cleanly, and functions, you may now note that you have reviewed the patch for all of these and turn the review tag to review+. <br />
* If you find a patch still needs work, you can explain in a clear and friendly manner what should be done, and turn the review tag to review-.<br />
* If a commit tag is there, leave it. <br />
* If a commit tag is not there, tell the person that they should add it if they are ready for the patch to be committed. (Sometimes people want patches to be reviewed before they are finished working on them!)<br />
<br />
[[Category: Development]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Database_Table:_acctcodeDatabase Table: acctcode2012-11-28T23:07:52Z<p>Exor674: Undo revision 10958 by Exor674Bot (talk)</p>
<hr />
<div>{{Database Table|name=acctcode|table type=global|repo=dw-free}}<br />
[[Description::This table stores data on valid invite codes and who has used them.]] The [[Database Table:acctinvite|acctinvite]] table stores the data on why they were made.<br />
= Definition =<br />
<source lang="sql"><br />
CREATE TABLE acctcode (<br />
acid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,<br />
userid INT UNSIGNED NOT NULL,<br />
rcptid INT UNSIGNED NOT NULL DEFAULT 0,<br />
auth CHAR(13) NOT NULL,<br />
timegenerate INT UNSIGNED NOT NULL,<br />
timesent INT UNSIGNED,<br />
email VARCHAR(255),<br />
reason VARCHAR(255),<br />
<br />
INDEX (userid),<br />
INDEX (rcptid)<br />
)<br />
</source><br />
{{Database Table Footer}}</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Database_Table:_ml_textDatabase Table: ml text2012-11-28T22:38:05Z<p>Exor674: Undo revision 10956 by Exor674 (talk)</p>
<hr />
<div>{{Database Table|name=ml_text|table type=global|need desc=1|repo=dw-free}}<br />
= Definition =<br />
<source lang="sql"><br />
CREATE TABLE ml_text (<br />
dmid TINYINT UNSIGNED NOT NULL,<br />
txtid INT UNSIGNED AUTO_INCREMENT NOT NULL,<br />
PRIMARY KEY (dmid, txtid),<br />
lnid SMALLINT UNSIGNED NOT NULL,<br />
itid SMALLINT UNSIGNED NOT NULL,<br />
INDEX (lnid, dmid, itid),<br />
text TEXT NOT NULL,<br />
userid INT UNSIGNED NOT NULL<br />
) TYPE=MYISAM<br />
</source><br />
{{Database Table Footer}}</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Database_Table:_ml_textDatabase Table: ml text2012-11-28T22:37:55Z<p>Exor674: </p>
<hr />
<div>{{Database Table|name=ml_text|table type=global|need desc=1|repo=dw-free}}<br />
= Definition =<br />
<source lang="sql"><br />
CREATE TABLE ml_text (<br />
dmid TINYINT UNSIGNED NOT NULL,<br />
txtid INT UNSIGNED AUTO_INCREMENT NOT NULL,<br />
PRIMARY KEY (dmid, txtid),<br />
lnid SMALLINT UNSIGNED NOT NULL,<br />
itid SMALLINT UNSIGNED NOT NULL,<br />
INDEX (lnid, dmid, itid),<br />
text TEXT NOT NULL,<br />
userid INT UNSIGNED NOT NULL<br />
) TYPE=MYISAM<br />
</source><br />
{{Database Table Footer}}<br />
<br />
la la la</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Moving_your_Dreamwidth_installation_to_use_GithubMoving your Dreamwidth installation to use Github2012-08-20T15:31:44Z<p>Exor674: /* set up dw-free */</p>
<hr />
<div>These instructions are for dreamhacks; those running your own instances I trust that you can modify to fit. This also assumes that you've gone through the "Set up git" instructions on https://help.github.com/articles/set-up-git<br />
<br />
= stop all workers and the server =<br />
<br />
killall worker-manager<br />
stop-apache<br />
<br />
= move your old $LJHOME aside (don't delete it though) =<br />
<br />
mv $LJHOME $LJHOME-xx<br />
<br />
= On github, fork dreamwidth's dw-free and dw-nonfree repositories =<br />
<br />
These are the two Dreamwidth repositories:<br />
<br />
* https://github.com/dreamwidth/dw-free<br />
* https://github.com/dreamwidth/dw-nonfree<br />
<br />
Here's a quick overview of how the repositories work together:<br />
<br />
* dw-free is the main repository and goes into $LJHOME<br />
* other repositories will go into $LJHOME/ext<br />
* personal config files go into $LJHOME/ext/local<br />
* Contents of $LJHOME will be live when you start the server<br />
* Contents of $LJHOME/ext will be used automatically (no syncing)<br />
<br />
Now it's time to set things up:<br />
<br />
= set up dw-free =<br />
<br />
# clone a copy of the repository onto your machine<br />
git clone https://github.com/USERNAME/dw-free.git $LJHOME<br />
cd $LJHOME<br />
<br />
# and let's make it aware of the dreamwidth repository so we can grab updates later<br />
git remote add dreamwidth https://github.com/dreamwidth/dw-free<br />
git fetch dreamwidth<br />
git branch --set-upstream develop dreamwidth/develop <br />
git branch --set-upstream master dreamwidth/master<br />
<br />
# now set up the folder for external repositories / modules<br />
mkdir $LJHOME/ext<br />
cd $LJHOME/ext<br />
<br />
= for dev servers (not clone sites) set up dw-nonfree =<br />
<br />
git clone https://github.com/USERNAME/dw-nonfree.git<br />
cd dw-nonfree<br />
git remote add dreamwidth https://github.com/dreamwidth/dw-nonfree<br />
git fetch dreamwidth<br />
git branch --set-upstream develop dreamwidth/develop <br />
git branch --set-upstream master dreamwidth/master<br />
cd ..<br />
<br />
## copy over your config files, and tell them to take priority<br />
## over config files in any of the repos<br />
mkdir --parents local/etc<br />
echo "highest" > local/.dir_scope<br />
cp $LJHOME-xx/etc/config* local/etc/<br />
<br />
= run checkconfig =<br />
<br />
cd $LJHOME<br />
bin/checkconfig.pl<br />
<br />
= non-dreamhack users: =<br />
<br />
* install .deb packages if given<br />
* install any modules without .deb packages via cpan (theSchwartz)<br />
* check any personal scripts you have to make sure they aren't affected by the directory config change<br />
* note: you won't need to run "dw sync" / "bin/cvsreport.pl --sync --cvs/--live" or anything of the sort anymore<br />
<br />
= start server again =<br />
<br />
start-apache<br />
<br />
[[Category: Development]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Moving_your_Dreamwidth_installation_to_use_GithubMoving your Dreamwidth installation to use Github2012-08-20T15:31:30Z<p>Exor674: /* set up dw-free */</p>
<hr />
<div>These instructions are for dreamhacks; those running your own instances I trust that you can modify to fit. This also assumes that you've gone through the "Set up git" instructions on https://help.github.com/articles/set-up-git<br />
<br />
= stop all workers and the server =<br />
<br />
killall worker-manager<br />
stop-apache<br />
<br />
= move your old $LJHOME aside (don't delete it though) =<br />
<br />
mv $LJHOME $LJHOME-xx<br />
<br />
= On github, fork dreamwidth's dw-free and dw-nonfree repositories =<br />
<br />
These are the two Dreamwidth repositories:<br />
<br />
* https://github.com/dreamwidth/dw-free<br />
* https://github.com/dreamwidth/dw-nonfree<br />
<br />
Here's a quick overview of how the repositories work together:<br />
<br />
* dw-free is the main repository and goes into $LJHOME<br />
* other repositories will go into $LJHOME/ext<br />
* personal config files go into $LJHOME/ext/local<br />
* Contents of $LJHOME will be live when you start the server<br />
* Contents of $LJHOME/ext will be used automatically (no syncing)<br />
<br />
Now it's time to set things up:<br />
<br />
= set up dw-free =<br />
<br />
# clone a copy of the repository onto your machine<br />
git clone https://github.com/USERNAME/dw-free.git $LJHOME<br />
cd $LJHOME<br />
<br />
# and let's make it aware of the dreamwidth repository so we can grab updates later<br />
git remote add dreamwidth https://github.com/dreamwidth/dw-free<br />
git fetch dreamwidth<br />
git branch --set-upstream develop dreamwidth/develop <br />
git branch --set-upstream master dreamwidth/master<br />
<br />
# now set up the folder for external repositories / modules<br />
mkdir $LJHOME/ext<br />
cd $LJHOME/ext<br />
<br />
= for dev servers (not clone sites) set up dw-nonfree =<br />
<br />
git clone https://github.com/USERNAME/dw-nonfree.git<br />
cd dw-nonfree<br />
git remote add dreamwidth https://github.com/dreamwidth/dw-nonfree<br />
git fetch dreamwidth<br />
git branch --set-upstream develop dreamwidth/develop <br />
git branch --set-upstream master dreamwidth/master<br />
cd ..<br />
<br />
## copy over your config files, and tell them to take priority<br />
## over config files in any of the repos<br />
mkdir --parents local/etc<br />
echo "highest" > local/.dir_scope<br />
cp $LJHOME-xx/etc/config* local/etc/<br />
<br />
= run checkconfig =<br />
<br />
cd $LJHOME<br />
bin/checkconfig.pl<br />
<br />
= non-dreamhack users: =<br />
<br />
* install .deb packages if given<br />
* install any modules without .deb packages via cpan (theSchwartz)<br />
* check any personal scripts you have to make sure they aren't affected by the directory config change<br />
* note: you won't need to run "dw sync" / "bin/cvsreport.pl --sync --cvs/--live" or anything of the sort anymore<br />
<br />
= start server again =<br />
<br />
start-apache<br />
<br />
[[Category: Development]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Moving_your_Dreamwidth_installation_to_use_GithubMoving your Dreamwidth installation to use Github2012-08-20T14:38:03Z<p>Exor674: /* for dev servers (not clone sites) set up dw-nonfree */</p>
<hr />
<div>These instructions are for dreamhacks; those running your own instances I trust that you can modify to fit. This also assumes that you've gone through the "Set up git" instructions on https://help.github.com/articles/set-up-git<br />
<br />
= stop all workers and the server =<br />
<br />
killall worker-manager<br />
stop-apache<br />
<br />
= move your old $LJHOME aside (don't delete it though) =<br />
<br />
mv $LJHOME $LJHOME-xx<br />
<br />
= On github, fork dreamwidth's dw-free and dw-nonfree repositories =<br />
<br />
These are the two Dreamwidth repositories:<br />
<br />
* https://github.com/dreamwidth/dw-free<br />
* https://github.com/dreamwidth/dw-nonfree<br />
<br />
Here's a quick overview of how the repositories work together:<br />
<br />
* dw-free is the main repository and goes into $LJHOME<br />
* other repositories will go into $LJHOME/ext<br />
* personal config files go into $LJHOME/ext/local<br />
* Contents of $LJHOME will be live when you start the server<br />
* Contents of $LJHOME/ext will be used automatically (no syncing)<br />
<br />
Now it's time to set things up:<br />
<br />
= set up dw-free =<br />
<br />
# clone a copy of the repository onto your machine<br />
git clone https://github.com/USERNAME/dw-free.git $LJHOME<br />
<br />
# and let's make it aware of the dreamwidth repository so we can grab updates later<br />
git remote add dreamwidth https://github.com/dreamwidth/dw-free<br />
git fetch dreamwidth<br />
git branch --set-upstream develop dreamwidth/develop <br />
git branch --set-upstream master dreamwidth/master<br />
<br />
# now set up the folder for external repositories / modules<br />
mkdir $LJHOME/ext<br />
cd $LJHOME/ext<br />
<br />
= for dev servers (not clone sites) set up dw-nonfree =<br />
<br />
git clone https://github.com/USERNAME/dw-nonfree.git<br />
cd dw-nonfree<br />
git remote add dreamwidth https://github.com/dreamwidth/dw-nonfree<br />
git fetch dreamwidth<br />
git branch --set-upstream develop dreamwidth/develop <br />
git branch --set-upstream master dreamwidth/master<br />
cd ..<br />
<br />
## copy over your config files, and tell them to take priority<br />
## over config files in any of the repos<br />
mkdir --parents local/etc<br />
echo "highest" > local/.dir_scope<br />
cp $LJHOME-xx/etc/config* local/etc/<br />
<br />
= run checkconfig =<br />
<br />
cd $LJHOME<br />
bin/checkconfig.pl<br />
<br />
= non-dreamhack users: =<br />
<br />
* install .deb packages if given<br />
* install any modules without .deb packages via cpan (theSchwartz)<br />
* check any personal scripts you have to make sure they aren't affected by the directory config change<br />
* note: you won't need to run "dw sync" / "bin/cvsreport.pl --sync --cvs/--live" or anything of the sort anymore<br />
<br />
= start server again =<br />
<br />
start-apache<br />
<br />
[[Category: Development]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Dreamwidth_Scratch_InstallationDreamwidth Scratch Installation2012-08-18T02:11:10Z<p>Exor674: /* If you are using lucid (or a similar/newer version of Ubuntu) */</p>
<hr />
<div>{{Update|text=Much of this is invalid since our GitHub move!}}<br />
<br />
Firstly, you'll need to set up a Linux system; these instructions assume Ubuntu 9.04 or up. Please let us know if you run into difficulties--we want to update this if people are having problems! We recommend coming into the #dreamwidth-dev channel on [[IRC]].<br />
<br />
After you set up, you can keep updated with the instructions on [[Dev Maintenance]].<br />
<br />
If you have difficulties or problems or suggestions for installation procedures, please report them to [http://bugs.dwscoalition.org/show_bug.cgi?id=61 Bug 61]. Also look at [[Production Setup: Webserver]]. <br />
<br />
== Bringing the system up to date ==<br />
<br />
Before you start installing anything else, you might want to make sure your system is up to date. You should log in as root and update the packages on your system:<br />
<br />
apt-get update<br />
apt-get upgrade<br />
<br />
If you want man pages on your system: <br />
<br />
apt-get install man<br />
<br />
== Installing necessary packages ==<br />
<br />
This will install necessary packages you'll need to run Dreamwidth (and some optional ones). You'll want to be root when running these commands.<br />
<br />
=== If you are using lucid (10.04) ===<br />
<br />
If you are using lucid, you can add [https://launchpad.net/~anall/+archive/dw exor674's ppa] and then install all the needed DW packages by doing<br />
<br />
add-apt-repository ppa:anall/dw<br />
apt-get update<br />
apt-get install mercurial subversion apache2-mpm-prefork \<br />
libapache2-mod-perl2 libapache2-mod-apreq2 libapache2-request-perl \<br />
mysql-server wget unzip links vim dreamwidth-packages gcc<br />
<br />
And that should pull in all required packages ( if it misses some Perl packages, feel free to poke <dwuser>exor674</dwuser><br />
<br />
Please note that these packages are unofficial, and you should only install them if you trust Dre, there is no guarantee that there will be no security issues in any of the packages in the ppa, if this concerns you, do not follow these instructions and instead follow the instructions below.<br />
<br />
=== Otherwise ===<br />
<br />
apt-get install mercurial git-core subversion apache2-mpm-prefork \<br />
libapache2-mod-perl2 libapache2-mod-apreq2 libapache2-request-perl \<br />
mysql-server wget unzip links vim libclass-autouse-perl \<br />
libcompress-zlib-perl libdatetime-perl libdigest-sha1-perl \<br />
libgd-gd2-perl libhtml-template-perl libwww-perl libmime-lite-perl \<br />
libnet-dns-perl liburi-perl libxml-simple-perl libclass-accessor-perl \<br />
libclass-data-inheritable-perl libclass-trigger-perl libcrypt-dh-perl \<br />
libmath-bigint-gmp-perl liburi-fetch-perl libgd-graph-perl \<br />
libgnupg-interface-perl libmail-gnupg-perl perlmagick \<br />
libproc-processtable-perl libsoap-lite-perl librpc-xml-perl \<br />
libstring-crc32-perl libtext-vcard-perl libxml-atom-perl libxml-rss-perl \<br />
libimage-size-perl libunicode-maputf8-perl libgtop2-dev build-essential \<br />
libnet-openid-consumer-perl libnet-openid-server-perl libyaml-perl \<br />
libcaptcha-recaptcha-perl libdbd-sqlite3-perl libtest-simple-perl \<br />
libtest-simpleunit-perl libtemplate-perl libterm-readkey-perl \<br />
libextutils-cbuilder-perl gcc libtest-most-perl \<br />
libbusiness-creditcard-perl liblwpx-paranoidagent-perl \<br />
libtheschwartz-perl libfile-type-perl<br />
<br />
This will download about 123MB of files and use around 446MB of disk space.<br />
<br />
Check whether these packages have actually installed. Later on in the process, if you find you are having inexplicable problems, try installing these again.<br />
<br />
If you have trouble with libdigest-sha1-perl not being available on Ubuntu 12.04, install dh-make-perl and do dh-make-perl --cpan digest-sha1 instead.<br />
<br />
If you can't get libcompress-zlib-perl, then libio-compress-perl is an acceptable substitute.<br />
<br />
=== Then ===<br />
<br />
[http://perl.apache.org/docs/2.0/user/install/install.html#Prerequisites Check] whether your system is using threaded MPMs or pre-fork. You will need the latter.<br />
<br />
If you have weird errors saying things like "Package mercurial is not available, but is referred to by another package.", try editing <tt>/etc/apt/sources.list</tt> and uncommenting the other repositories and then doing an <tt>apt-get</tt> update. Ran into this problem on a fresh install of <tt>jaunty</tt> on Linode.<br />
<br />
You will also want to install some perl libraries with CPAN. Defaults during CPAN's setup should be okay.<br />
<br />
Before installing, check if your CPAN shell has 'make' at the correct location. Run <tt>which make</tt>, then compare the results with running <tt>o conf make</tt> at the CPAN shell (to run the CPAN shell, type <tt>cpan</tt>.)<br />
<br />
The perl libraries are:<br />
<br />
cpan Bundle::CPAN<br />
cpan GTop # this will guide you through setting up CPAN<br />
cpan Unicode::CheckUTF8<br />
cpan Captcha::reCAPTCHA<br />
cpan Hash::MultiValue<br />
cpan MogileFS::Client # this is necessary even if you don't use MogileFS<br />
cpan TheSchwartz::Worker::SendEmail<br />
<br />
See [http://wiki.dwscoalition.org/notes/Bundle::CPAN Bundle::CPAN] if you are having trouble installing Bundle::CPAN<br />
<br />
You will also want to install and configure Postfix so your DW can send out email:<br />
<br />
apt-get install postfix<br />
<br />
If you choose not to configure on the install, you can do so later with:<br />
<br />
dpkg-reconfigure postfix<br />
<br />
== Setting up the DW user account ==<br />
<br />
{{Note|text=If you still find yourself unable to make sudo commands even after <code>usermod</code>, please see [[Making your DW user a sudo account]] for alternatives.}} Create a user account that will be used for the code. This can be your own personal account if you wish, but it is recommended that you setup a new user that is only going to be used for running the code. That way if you have any insecure code running (i.e., a bad page, or a patch you're working on doesn't have security implemented yet) you don't have to worry about someone getting access to your personal files.<br />
<br />
To set up the user account:<br />
<br />
adduser <i>username</i><br />
<br />
Fill out the user's info and put them in the sudo group:<br />
<br />
usermod -a -G sudo <i>username</i><br />
<br />
So, to use <code>dw</code> as a username:<br />
<br />
adduser dw<br />
usermod -a -G sudo dw<br />
<br />
Now, we will get the $LJHOME variable set. Log into your user:<br />
<br />
su - dw<br />
<br />
We will assume that you use the bash shell. If you do not, then this section does not apply. You will have to find the shell specific way of setting environment variables.<br />
<br />
For bash, you should look in the home directory of the user account you just created for a file .profile. You will want to add one more line to this file:<br />
<br />
export LJHOME=/home/dw<br />
<br />
Or whatever your user account's home directory is. Yes, the variable is named LJHOME. We'll live with it for now. ;-)<br />
<br />
Test this. Log out of your user account and log back in, then type:<br />
<br />
echo $LJHOME<br />
<br />
You should see /home/dw or whatever you set it to. If you don't, then you didn't get the export line in the right place. (Hey, I'm not here to teach basic sysadmin stuff. You should know how to set environment variables.)<br />
<br />
== Downloading the Dreamwidth code ==<br />
<br />
You should do this as the user you want to run Dreamwidth as. If you are root, you can switch with:<br />
<br />
su - dw<br />
<br />
We have put together a bootstrap script that will download all of the code. You can obtain this file with your favorite tool (wget, curl, etc):<br />
<br />
wget http://hg.dwscoalition.org/dw-free/raw-file/tip/bin/bootstrap.pl<br />
<br />
The next step is fairly automatic. Assuming that your $LJHOME environment variable is good to go, you should be able to just do this:<br />
<br />
perl bootstrap.pl<br />
<br />
The script will start doing some work. It should start out by saying 'seems we need to start at the beginning', which is what you expect. This may take some time while it checks out various packages and repositories from various locations.<br />
<br />
When it's done, it will say so. It will also say to delete the bootstrap script: <br />
<br />
rm bootstrap.pl<br />
<br />
Do a quick ls in $LJHOME to see if you have a bunch of new directories like cvs, cgi-bin, htdocs, etc. If you do, huzzah! You now have the code checked out.<br />
<br />
If you don't, figure out what step didn't work, and try again. All else fails, hit the mailing list or find someone on IRC.<br />
<br />
=== dw-nonfree ===<br />
<br />
{{Warn|text=This should not be used on installations that are not Dreamwidth development installations.}} <br />
<br />
If you are doing development on Dreamwidth custom files (what few there are), then you will need to do another step. This is fairly manual, the bootstrap script does not handle this process for you. But assuming that you did the above steps, you can get dw-nonfree code like this:<br />
<br />
cd $LJHOME/cvs<br />
hg clone http://hg.dwscoalition.org/dw-nonfree dw-nonfree<br />
cp dw-nonfree/cvs/multicvs-local.conf .<br />
<br />
You may want to look at [[Keeping your site configs from wiping during updates]] if you want to keep your config files from getting wiped during subsequent updates if you're using <tt>dw-nonfree</tt>.<br />
<br />
=== Updating to the tip ===<br />
<br />
Because all named branches have been closed as of July 19th 2010, you should have the most up-to-date code already checked out, although you may need to do <code>hg update -C tip</code> to get the most recent changes.<br />
<br />
Some repositories have changed locations and must be manually removed if they already exist in the cvs directory.<br />
<br />
cd $LJHOME/cvs/dw-free<br />
hg update -C tip<br />
cd $LJHOME<br />
bin/cvsreport.pl -sync -cvsonly<br />
rm -rf cvs/perlbal/<br />
rm -rf cvs/js/<br />
bin/cvsreport.pl --checkout<br />
bin/cvsreport.pl -sync -cvsonly<br />
<br />
<br />
You need to do the last line twice, since the multicvs.conf file has changed since then. Any future updates should now work as described on the [[Dev Maintenance]] page. You will also want to delete files that no longer exist in the repository:<br />
<br />
{{Snippets/Deleting removed files}}<br />
<br />
== Database setup ==<br />
<br />
You should have a local MySQL installation and know the root login to the database. To sign into MySQL as root:<br />
<br />
mysql -u root -p <br />
<br />
These MySQL commands wil create your development database:<br />
<br />
create database dw;<br />
grant all on dw.* to 'dw'@'localhost' identified by 'somePassword';<br />
quit;<br />
<br />
You might want to pick a more appropriate database username/database name/password.<br />
<br />
== Editing the config files ==<br />
<br />
Next you need to configure the site configuration scripts. This is probably the most tricky part of the whole process, since there are so many things you can tweak. However, you can get by with just tweaking etc/config-local.pl and etc/config-private.pl for now:<br />
<br />
cd $LJHOME<br />
cp doc/config-local.pl.txt etc/config-local.pl<br />
vim etc/config-local.pl<br />
<br />
Or use your editor of choice, of course. Do a search for the phrase 'CHANGE THIS' which occurs once, before the block of human readable text you should use to describe your development installation.<br />
<br />
Note that the <var>$IS_DEV_SERVER</var> flag is set to 1 in the template given. Be sure to set this to 0 for a production site, as there are big security issues involved with leaving the flag set to 1. <br />
<br />
Next, you need to change your local variables in the <tt>config-private.pl</tt> file. This is where your passwords are configured, as well as many of the variables which define your domain. Do a search for the phrase 'CHANGETHIS'(note the slightly different spacing). You will want to change at least your <var>$DOMAIN</var> and the <var>%DBINFO</var> structure.<br />
<br />
cd $LJHOME<br />
cp doc/config-private.pl.txt etc/config-private.pl<br />
chmod go-rwx etc/config-private.pl<br />
vim etc/config-private.pl<br />
<br />
In this file, uncomment the lines in the <var>%DBINFO</var> blocks (if they are commented out), and place your database password in quotes on the "pass => ," line, before the comma. For example:<br />
<br />
%DBINFO = (<br />
master => {<br />
pass => 'mypassword',<br />
},<br />
);<br />
<br />
Note that there are two DBINFO blocks; get them both.<br />
<br />
The base configuration file is under source control, and is already in <tt>etc/config.pl</tt>. You should not need to change anything very much in this, but you might have to change some stuff. If you find you do have to touch this, please see [http://bugs.dwscoalition.org/show_bug.cgi?id=165] first. Then you can edit it:<br />
<br />
vim etc/config.pl<br />
<br />
== Make sure things are working with checkconfig.pl ==<br />
<br />
Now, you need to see if everything is working. <br />
<br />
If you've setup the files as indicated above, run this command:<br />
<br />
cd $LJHOME<br />
bin/checkconfig.pl --no=ljconfig<br />
<br />
If you installed everything given at the top of this page, you should find you have no missing modules. Congratulations! If you don't have all the modules, this is where you need some systems specific knowledge for your system. You will need to install whatever modules are missing. If you get well and truly stuck, find someone on IRC or the mailing list.<br />
<br />
== Populate database with initial data ==<br />
<br />
There are a few commands you can now run to install the database. Just run these and watch for errors.<br />
<br />
<b>Note:</b> If make_system.pl says it can't give the system user admin [[privileges]], something has gone wrong with your database population, even if there were no errors.<br />
<br />
$LJHOME/bin/upgrading/update-db.pl -r --innodb<br />
$LJHOME/bin/upgrading/update-db.pl -r --cluster=all --innodb<br />
$LJHOME/bin/upgrading/update-db.pl -p<br />
<br />
(That step will ask you for a password for the [[System account]]. You can change it later by logging in as system, so just give it something for now.)<br />
<br />
$LJHOME/bin/upgrading/make_system.pl<br />
$LJHOME/bin/upgrading/texttool.pl load<br />
<br />
== Configure Apache ==<br />
<br />
This step will need to be done as the root user. Below is the Apache 2 configuration running on the Dreamwidth staging site. Put this in a file named "stage" in /etc/apache2/conf.d:<br />
<br />
User dw<br />
Group dw<br />
UseCanonicalName off<br />
<br />
StartServers 1<br />
MaxSpareServers 2<br />
MinSpareServers 1<br />
<br />
DocumentRoot /home/dw/htdocs<br />
<br />
PerlSetEnv LJHOME /home/dw<br />
PerlPassEnv LJHOME<br />
<br />
PerlRequire /home/dw/cgi-bin/modperl.pl<br />
<br />
Then disable the default site:<br />
<br />
a2dissite default<br />
<br />
You might also have to enable the Perl Apache Request module:<br />
<br />
a2enmod apreq<br />
<br />
If you don't want this warning:<br />
<br />
[Thu Jan 15 01:46:54 2009] [warn] NameVirtualHost *:80 has no VirtualHosts<br />
... waiting [Thu Jan 15 01:46:55 2009] [warn] NameVirtualHost *:80 has no VirtualHosts<br />
...done.<br />
<br />
Then use <code>vim /etc/apache2/ports.conf</code> and put a # in front of the NameVirtualHost line:<br />
<br />
# NameVirtualHost *:80<br />
<br />
Restart the server:<br />
<br />
/etc/init.d/apache2 restart<br />
<br />
== Now what? ==<br />
<br />
=== Have a look at your new DW instance ===<br />
<br />
Congratulations! You now have a working (though minimal) Dreamwidth install. If you point your web browser at your server, you should see a bare-looking welcome page.<br />
<br />
=== Further setup ===<br />
<br />
There are lots of other articles on setting up and customizing your DW install in the [[:Category: Dreamwidth Installation|DW Installation]] category, including:<br />
<br />
* [[TheSchwartz Setup]] - TheSchwartz is needed for a handful of features - notably comment posting<br />
* [[Subdomain setup]]<br />
* [[Statistics setup]]<br />
* [[Generating documentation]]<br />
* [[Allow users to register]]<br />
<br />
There is a list of wanted how-tos at [[Installation Wanted How-To]], in case you need to add something to the list.<br />
<br />
=== Starting development ===<br />
<br />
The list of things that need doing are in [http://bugs.dwscoalition.org/buglist.cgi?query_format=advanced&short_desc_type=allwordssubstr&short_desc=&long_desc_type=substring&long_desc=&bug_file_loc_type=allwordssubstr&bug_file_loc=&keywords_type=allwords&keywords=&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailtype1=substring&email1=&emailassigned_to2=1&emailreporter2=1&emailqa_contact2=1&emailcc2=1&emailtype2=substring&email2=&bugidtype=include&bug_id=&chfie Bugzilla]. You can keep your code up to date with the instructions in [[Dev Maintenance]]. Instructions on submitting patches are in [[Dev Patches]].<br />
<br />
[[Category: Dreamwidth Installation]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/File_headersFile headers2012-07-17T05:00:43Z<p>Exor674: </p>
<hr />
<div>[[Category: Development]]<br />
<br />
All files you create as part of your DW development work should have an appropriate header file. If you completely rewrite a file that used to be part of LiveJournal's "livejournal" repository, but no original code remains, you should indicate that as well (along with a link to LJ's license).<br />
<br />
Right now, we don't have a header for code that was from the "livejournal" repo and has been modified by us -- you can just leave those blank.<br />
<br />
==Copyright==<br />
<br />
Copyright is maintained by the Dreamwidth Studios, LLC. organization, as all committers/contributors should have signed a contributor's agreement to properly grant license to their work. But authors should be noted for credit purposes.<br />
<br />
If the file was modified in the years after it was created, or modified on more than one year, the copyright years should reflect that, but always in the first-last form, even if they're consecutive years or there would be gaps in the list. For instance, a file created in 2009 and modified in 2010 should have "2009-2010" instead of "2009, 2010"; a file created in 2009 and modified in 2010 and 2012 (but not 2011) should have "2009-2012" instead of "2009, 2010, 2012" or "2009-2010, 2012".<br />
<br />
==Authors list==<br />
<br />
Rule of thumb: if you are writing a new file, you can add yourself to the authors. If you are editing an existing file, you should only add yourself to the authors list if you are making significant changes (>10% or 100 lines, whichever is less). (Fixing typos does not qualify one to be a called out author of a file, sorry!)<br />
<br />
Exception: if you're splitting a file into two or more new files and deleting the original (eg, when converting a site page from BML to TT), keep the original authors and add yourself to the list for all new files, even if one of the new files ends up with more than 90% of the code that was in the original file.<br />
<br />
Note: in both cases, author call-outs are just name and email address. Even for major changes, go to the repo if you want to know who did what.<br />
<br />
==Example headers==<br />
===Brand-new file, dw-free===<br />
<br />
<source lang="perl">#!/usr/bin/perl<br />
#<br />
# DW::Setting::AwesomeNewFeature<br />
#<br />
# This file is the accompanying settings package for AwesomeNewFeature. It allows users to set and<br />
# clear their setting choices.<br />
#<br />
# Authors:<br />
# J. Random Hacker <jrh@example.org><br />
#<br />
# Copyright (c) 2012 by Dreamwidth Studios, LLC.<br />
#<br />
# This program is free software; you may redistribute it and/or modify it under<br />
# the same terms as Perl itself. For a copy of the license, please reference<br />
# 'perldoc perlartistic' or 'perldoc perlgpl'.<br />
#</source><br />
<br />
===Brand new file, dw-nonfree===<br />
<br />
<source lang="perl">#!/usr/bin/perl<br />
#<br />
# site/world-domination.bml<br />
#<br />
# This file explains Dreamwidth's plans for world domination. Be sure to keep it updated!<br />
#<br />
# Authors:<br />
# J. Random Hacker <jrh@example.org><br />
#<br />
# Copyright (c) 2012 by Dreamwidth Studios, LLC.<br />
#<br />
# This program is NOT free software or open-source; you can use it as an<br />
# example of how to implement your own site-specific extensions to the<br />
# Dreamwidth Studios open-source code, but you cannot use it on your site<br />
# or redistribute it, with or without modifications.<br />
#</source><br />
<br />
===Header for file that was lj code and has been totally rewritten===<br />
<br />
<source lang="perl">#!/usr/bin/perl<br />
#<br />
# LJ::Setting::RevampedFeature<br />
#<br />
# LJ::Setting module for playing with RevampedFeature.<br />
#<br />
# Author:<br />
# J. Random Hacker <jrh@example.org><br />
#<br />
# Copyright (c) 2012 by Dreamwidth Studios, LLC.<br />
#<br />
# The original version of this program was authored by LiveJournal.com<br />
# and distributed under the terms of the license supplied by LiveJournal Inc,<br />
# which can be found at:<br />
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt<br />
#<br />
# This program has since been wholly rewritten by Dreamwidth Studios.<br />
# No parent code remains.<br />
#<br />
# This program is free software; you may redistribute it and/or modify it under<br />
# the same terms as Perl itself. For a copy of the license, please reference<br />
# 'perldoc perlartistic' or 'perldoc perlgpl'.<br />
#</source><br />
<br />
===Header for files that were livejournal code and have been partially rewritten/changed===<br />
<br />
Right now, these files don't have any header. (We haven't decided what the header should be.) If you edit one of those files, you can leave it without a header.</div>Exor674//wiki.dreamwidth.net/wiki/index.php/File_headersFile headers2012-07-17T04:56:08Z<p>Exor674: </p>
<hr />
<div>[[Category: Development]]<br />
<br />
All files you create as part of your DW development work should have an appropriate header file. If you completely rewrite a file that used to be part of LiveJournal's "livejournal" repository, but no original code remains, you should indicate that as well (along with a link to LJ's license).<br />
<br />
Right now, we don't have a header for code that was from the "livejournal" repo and has been modified by us -- you can just leave those blank.<br />
<br />
==Copyright==<br />
<br />
Copyright is maintained by the Dreamwidth Studios, LLC. organization, as all committers/contributors should have signed a contributor's agreement to properly grant license to their work. But authors should be noted for credit purposes.<br />
<br />
If the file was modified in the years after it was created, or modified on more than one year, the copyright years should reflect that, but always in the first-last form, even if they're consecutive years or there would be gaps in the list. For instance, a file created in 2009 and modified in 2010 should have "2009-2010" instead of "2009, 2010"; a file created in 2009 and modified in 2010 and 2012 (but not 2011) should have "2009-2012" instead of "2009, 2010, 2012" or "2009-2010, 2012".<br />
<br />
==Authors list==<br />
<br />
Rule of thumb: if you are writing a new file, you can add yourself to the authors. If you are editing an existing file, you should only add yourself to the authors list if you are making significant changes (>10% or 100 lines, whichever is less). (Fixing typos does not qualify one to be a called out author of a file, sorry!)<br />
<br />
Exception: if you're splitting a file into two or more new files and deleting the original (eg, when converting a site page from BML to TT), keep the original authors and add yourself to the list for all new files, even if one of the new files ends up with more than 90% of the code that was in the original file.<br />
<br />
Note: in both cases, author call-outs are just name and email address. Even for major changes, go to the repo if you want to know who did what.<br />
<br />
==Example headers==<br />
===Brand-new file, dw-free===<br />
<br />
<source lang="perl">#!/usr/bin/perl<br />
#<br />
# DW::Setting::AwesomeNewFeature<br />
#<br />
# This file is the accompanying settings package for AwesomeNewFeature. It allows users to set and<br />
# clear their setting choices.<br />
#<br />
# Authors:<br />
# J. Random Hacker <jrh@example.org><br />
#<br />
# Copyright (c) {{CURRENTYEAR}} by Dreamwidth Studios, LLC.<br />
#<br />
# This program is free software; you may redistribute it and/or modify it under<br />
# the same terms as Perl itself. For a copy of the license, please reference<br />
# 'perldoc perlartistic' or 'perldoc perlgpl'.<br />
#</source><br />
<br />
===Brand new file, dw-nonfree===<br />
<br />
<source lang="perl">#!/usr/bin/perl<br />
#<br />
# site/world-domination.bml<br />
#<br />
# This file explains Dreamwidth's plans for world domination. Be sure to keep it updated!<br />
#<br />
# Authors:<br />
# J. Random Hacker <jrh@example.org><br />
#<br />
# Copyright (c) {{CURRENTYEAR}} by Dreamwidth Studios, LLC.<br />
#<br />
# This program is NOT free software or open-source; you can use it as an<br />
# example of how to implement your own site-specific extensions to the<br />
# Dreamwidth Studios open-source code, but you cannot use it on your site<br />
# or redistribute it, with or without modifications.<br />
#</source><br />
<br />
===Header for file that was lj code and has been totally rewritten===<br />
<br />
<source lang="perl">#!/usr/bin/perl<br />
#<br />
# LJ::Setting::RevampedFeature<br />
#<br />
# LJ::Setting module for playing with RevampedFeature.<br />
#<br />
# Author:<br />
# J. Random Hacker <jrh@example.org><br />
#<br />
# Copyright (c) {{CURRENTYEAR}} by Dreamwidth Studios, LLC.<br />
#<br />
# The original version of this program was authored by LiveJournal.com<br />
# and distributed under the terms of the license supplied by LiveJournal Inc,<br />
# which can be found at:<br />
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt<br />
#<br />
# This program has since been wholly rewritten by Dreamwidth Studios.<br />
# No parent code remains.<br />
#<br />
# This program is free software; you may redistribute it and/or modify it under<br />
# the same terms as Perl itself. For a copy of the license, please reference<br />
# 'perldoc perlartistic' or 'perldoc perlgpl'.<br />
#</source><br />
<br />
===Header for files that were livejournal code and have been partially rewritten/changed===<br />
<br />
Right now, these files don't have any header. (We haven't decided what the header should be.) If you edit one of those files, you can leave it without a header.</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Routing_and_Template_ToolkitRouting and Template Toolkit2012-02-16T01:55:26Z<p>Exor674: /* Including a file with a temporary ML scope change */ SO MUCH EASIER!</p>
<hr />
<div>Dreamwidth is currently in the process of moving away from [[BML]] and towards a system of routing that employs [http://template-toolkit.org/ Template Toolkit] for templates.<br />
<br />
== The Basics ==<br />
<br />
Making a page with the routing and Template Toolkit system requires a few things:<br />
<br />
# Define a URL string or pattern<br />
# Create a handler function, attached to the URL string or pattern<br />
# Make any needed templates <br />
<br />
The URL(s) and handler function(s) go into the controller file, which will be somewhere in <tt>$LJHOME/cgi-bin/DW/Controller</tt>.<br />
<br />
The template(s) will go into <tt>$LJHOME/views</tt>.<br />
<br />
=== Defining a URL ===<br />
<br />
First, you need to define a URL in a controller file in <tt>$LJHOME/cgi-bin/DW/Controller</tt>. This will tell the system what handler function to use for a given URL pattern.<br />
<br />
Here are some examples of registering a URL. There are two main functions to do this with, <tt>register_string</tt> and <tt>register_regex</tt>.<br />
<br />
<source lang="perl"><br />
# This registers a static string, which is an application page.<br />
DW::Routing->register_string( '/misc/whereami', \&whereami_handler, <br />
app => 1 );<br />
<br />
# This registers a regular expression. Later, we can get the <br />
# contents inside the parentheticals. This lets us make "clean" urls <br />
# like /nav/read and /nav/create that are equivalent to<br />
# /nav?cat=create<br />
DW::Routing->register_regex( qr!^/nav(?:/([a-z]*))?$!, \&nav_handler,<br />
app => 1 );<br />
<br />
# This registers a user page, so, for example, it would be accessed at<br />
# username.dreamwidth.org/data/edges<br />
# This also explicitly sets our default output format to be JSON.<br />
DW::Routing->register_string( "/data/edges", \&edges_handler, <br />
user => 1, format => 'json' );</source><br />
<br />
==== Defining an index page ====<br />
<br />
<source lang="perl"><br />
# This is all you have to do<br />
DW::Routing->register_string( '/something/index', \&index_handler, <br />
app => 1 );<br />
</source><br />
<br />
This registers all of /something, /something/ and /something/index<br />
<br />
=== Creating a handler function ===<br />
<br />
The handler function tells the system what to do with the browser request it is receiving. It's also defined in the controller file you register the URL in, in <tt>$LJHOME/cgi-bin/DW/Controller</tt>. The handler function will look up things, define variables we need for the page, and pass them onto the template.<br />
<br />
I'm going to use the Nav controller as an example. You can see the entire page at [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Controller/Nav.pm DW::Controller::Nav.pm].<br />
<br />
We define our URL in the same file we place the handler function, so we'll start there:<br />
<br />
<source lang="perl">package DW::Controller::Nav;<br />
<br />
use strict;<br />
use warnings;<br />
use DW::Controller;<br />
use DW::Routing;<br />
use DW::Template;<br />
use DW::Logic::MenuNav;<br />
use JSON;<br />
<br />
# Defines the URL for routing. I could use register_string( '/nav' ... ) if I didn't want to capture arguments<br />
# This is an application page, not a user styled page, and the default format is HTML (ie, /nav gives /nav.html)<br />
DW::Routing->register_regex( qr!^/nav(?:/([a-z]*))?$!, \&nav_handler, app => 1 );</source><br />
<br />
First, I want to get the options, subpatterns, and request:<br />
<br />
<source lang="perl"># handles menu nav pages<br />
sub nav_handler {<br />
my ( $opts, $cat ) = shift;<br />
my $r = DW::Request->get;</source><br />
<br />
Remember that subpattern I made in my URL regex? I'll figure out if I'm using it or an argument here.<br />
<br />
<source lang="perl"><br />
# Check for a category like nav/read, then for a ?cat=read argument, else no category<br />
$cat ||= $r->get_args->{cat} || '';</source><br />
<br />
Here, I'm doing error checking. If I don't get back the array of menu hashes, I'm going to serve an error page that contains a translated error message. Notice how I have to define the whole path to the error message, and can't just use ".error.invalidcat".<br />
<br />
<source lang="perl"> # this function returns an array reference of menu hashes<br />
my $menu_nav = DW::Logic::MenuNav->get_menu_display( $cat )<br />
or return error_ml( '/nav.tt.error.invalidcat' );</source><br />
<br />
I'm getting some cruft that is needed by the real menu that I don't want to display on this page, so I'll go through all the menus and strip out HTML from the titles. Ideally there would be a Template Toolkit filter that could do this, but I have not found one yet; we may have to make one.<br />
<br />
<source lang="perl"> # this data doesn't need HTML in the titles, like in the real menu<br />
for my $menu ( @$menu_nav ) {<br />
for my $item ( @{ $menu->{items} } ) {<br />
$item->{text} = LJ::strip_html( $item->{text} );<br />
}<br />
}</source><br />
<br />
This is a nifty part. I can display the contents of this page either as HTML or as JSON (which is made for machine parsing), with very very little additional code.<br />
<br />
The first one is how to return something as JSON. I just print out the object conversion to the request and return OK.<br />
<br />
<source lang="perl"> # display according to the format<br />
my $format = $opts->format;<br />
if ( $format eq 'json' ) {<br />
# this prints out the menu navigation as JSON and returns<br />
$r->print( JSON::objToJson( $menu_nav ) );<br />
return $r->OK;</source><br />
<br />
The HTML format takes a bit more work. We want to pass in more variables to the template and return the rendered template. <br />
<br />
Here we go preparing the variables:<br />
<br />
<source lang="perl"> } elsif ( $format eq 'html' ) {<br />
# these variables will get passed to the template<br />
my $vars = {<br />
menu_nav => $menu_nav,<br />
cat => $cat,<br />
};<br />
<br />
$vars->{cat_title} = $menu_nav->[0]->{title} if $cat;</source><br />
<br />
And this is the call to render our template (more on making that next):<br />
<br />
<source lang="perl"> # Now we tell it what template to render and pass in our variables<br />
return DW::Template->render_template( 'nav.tt', $vars );</source><br />
<br />
If my format isn't HTML or JSON, we throw a 404.<br />
<br />
<source lang="perl"> } else { <br />
# return 404 for an unknown format<br />
return $r->NOT_FOUND;<br />
}<br />
}<br />
<br />
1;</source><br />
<br />
And that's the handler!<br />
<br />
=== Creating a template ===<br />
<br />
The template will be placed somewhere logical in <tt>$LJHOME/views</tt>. This one will be <tt>[http://hg.dwscoalition.org/dw-free/file/tip/views/nav.tt nav.tt]</tt>. Currently, translation strings will go in <tt>nav.tt.text</tt>.<br />
<br />
This section is a comment:<br />
<br />
<source lang="html4strict"><br />
[%# nav.tt<br />
<br />
Page that shows the sub-level navigation links given the top-level navigation header<br />
<br />
%]</source><br />
<br />
This section sets the title of the page. If we have a category, it will be the category title. Otherwise, it will use the ML translation of the title for the page.<br />
<br />
<source lang="html4strict">[%- IF cat; sections.title = cat_title; ELSE; sections.title = '.title' | ml; END -%]</source><br />
<br />
This section creates the menu sections, applying code for each menu and each item in each menu. If this isn't only displaying a category, it makes the title. You can see the [http://template-toolkit.org/docs/manual/Filters.html#section_html html filter] being used in places like <tt>[% menu.title | html %]</tt> to ensure that all HTML is safe, and the [http://template-toolkit.org/docs/manual/Filters.html#section_url url filter] being used in <tt>[% menu_item.url | url %]</tt> to make sure that URL will be validly encoded.<br />
<br />
<source lang="html4strict">[% FOREACH menu = menu_nav %]<br />
[% IF NOT cat %]<h2 class="[% menu.name %]">[% menu.title | html %]</h2>[% END %]<br />
<ul><br />
[% FOREACH menu_item = menu.items %]<br />
<li><a href="[% menu_item.url | url %]">[% menu_item.text | html %]</a></li><br />
[% END %]<br />
</ul><br />
[% END %]<br />
</source><br />
<br />
That template is all that's required to render the HTML for the nav page! You can see it [http://www.dreamwidth.org/nav live on Dreamwidth].<br />
<br />
== Standard tricks ==<br />
<br />
=== Inserting variables into translation strings ===<br />
<br />
The example above sneaked in the way to use the translation system from within templates, by doing:<br />
<br />
<source lang="html4strict">[%- IF cat; sections.title = cat_title; ELSE; sections.title = '.title' | ml; END -%]</source><br />
<br />
But what if you needed to insert something, such as the sitename or a username, into a string? Here's a fragment that does the latter, drawn from [http://hg.dwscoalition.org/dw-free/file/tip/views/misc/pubkey.tt views/misc/pubkey.tt]:<br />
<br />
<source lang="html4strict">[% '.label' | ml(user = u.ljuser_display) %]</source><br />
<br />
=== Including a file with a temporary ML scope change ===<br />
<br />
<source lang="html4strict"><br />
[% dw.scoped_include('stats/site.tt'); %]<br />
</source><br />
<br />
=== Specifying needed CSS/JS files ===<br />
<br />
<source lang="html4strict"><br />
[%- CALL dw.set_active_resource_group( 'jquery' ) -%]<br />
[%- dw.need_res( 'stc/kitten.css' ) -%]<br />
[%- dw.need_res( 'js/ponies.js' ) -%]<br />
[%- dw.need_res( ( group => 'jquery' ), 'js/sparkle.js' ) -%]<br />
</source><br />
<br />
Note: ignore the first and last if your page doesn't use jQuery.<br />
<br />
For CSS, remember to used the [http://www.dreamwidth.org/dev/classes standardized classes] as much as possible.<br />
<br />
== References ==<br />
<br />
* [http://template-toolkit.org/docs/index.html Template Toolkit Documentation]<br />
* [http://docs.dreamwidth.net/DW/Request.html DW::Request API Documentation]<br />
* [http://docs.dreamwidth.net/DW/Routing.html DW::Routing API Documentation]<br />
* [http://docs.dreamwidth.net/DW/Template.html DW::Template API Documentation]<br />
* [http://docs.dreamwidth.net/DW/Template/Plugin.html Plugin Documentation]<br />
* [http://docs.dreamwidth.net/DW/Template/Filters.html Filter Documentation]<br />
<br />
=== Code Modules ===<br />
<br />
* [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Controller.pm DW::Controller]<br />
* [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Request.pm DW::Request]<br />
* [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Routing.pm DW::Routing]<br />
* [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Template.pm DW::Template]<br />
* [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Template/Plugin.pm DW::Template::Plugin]<br />
* [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Template/Filters.pm DW::Template::Filters]<br />
* [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Template/Plugin/FormHTML.pm DW::Template::Plugin::FormHTML]<br />
<br />
[[Category: Development]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Routing_and_Template_ToolkitRouting and Template Toolkit2012-02-16T01:44:39Z<p>Exor674: /* Creating a handler function */</p>
<hr />
<div>Dreamwidth is currently in the process of moving away from [[BML]] and towards a system of routing that employs [http://template-toolkit.org/ Template Toolkit] for templates.<br />
<br />
== The Basics ==<br />
<br />
Making a page with the routing and Template Toolkit system requires a few things:<br />
<br />
# Define a URL string or pattern<br />
# Create a handler function, attached to the URL string or pattern<br />
# Make any needed templates <br />
<br />
The URL(s) and handler function(s) go into the controller file, which will be somewhere in <tt>$LJHOME/cgi-bin/DW/Controller</tt>.<br />
<br />
The template(s) will go into <tt>$LJHOME/views</tt>.<br />
<br />
=== Defining a URL ===<br />
<br />
First, you need to define a URL in a controller file in <tt>$LJHOME/cgi-bin/DW/Controller</tt>. This will tell the system what handler function to use for a given URL pattern.<br />
<br />
Here are some examples of registering a URL. There are two main functions to do this with, <tt>register_string</tt> and <tt>register_regex</tt>.<br />
<br />
<source lang="perl"><br />
# This registers a static string, which is an application page.<br />
DW::Routing->register_string( '/misc/whereami', \&whereami_handler, <br />
app => 1 );<br />
<br />
# This registers a regular expression. Later, we can get the <br />
# contents inside the parentheticals. This lets us make "clean" urls <br />
# like /nav/read and /nav/create that are equivalent to<br />
# /nav?cat=create<br />
DW::Routing->register_regex( qr!^/nav(?:/([a-z]*))?$!, \&nav_handler,<br />
app => 1 );<br />
<br />
# This registers a user page, so, for example, it would be accessed at<br />
# username.dreamwidth.org/data/edges<br />
# This also explicitly sets our default output format to be JSON.<br />
DW::Routing->register_string( "/data/edges", \&edges_handler, <br />
user => 1, format => 'json' );</source><br />
<br />
==== Defining an index page ====<br />
<br />
<source lang="perl"><br />
# This is all you have to do<br />
DW::Routing->register_string( '/something/index', \&index_handler, <br />
app => 1 );<br />
</source><br />
<br />
This registers all of /something, /something/ and /something/index<br />
<br />
=== Creating a handler function ===<br />
<br />
The handler function tells the system what to do with the browser request it is receiving. It's also defined in the controller file you register the URL in, in <tt>$LJHOME/cgi-bin/DW/Controller</tt>. The handler function will look up things, define variables we need for the page, and pass them onto the template.<br />
<br />
I'm going to use the Nav controller as an example. You can see the entire page at [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Controller/Nav.pm DW::Controller::Nav.pm].<br />
<br />
We define our URL in the same file we place the handler function, so we'll start there:<br />
<br />
<source lang="perl">package DW::Controller::Nav;<br />
<br />
use strict;<br />
use warnings;<br />
use DW::Controller;<br />
use DW::Routing;<br />
use DW::Template;<br />
use DW::Logic::MenuNav;<br />
use JSON;<br />
<br />
# Defines the URL for routing. I could use register_string( '/nav' ... ) if I didn't want to capture arguments<br />
# This is an application page, not a user styled page, and the default format is HTML (ie, /nav gives /nav.html)<br />
DW::Routing->register_regex( qr!^/nav(?:/([a-z]*))?$!, \&nav_handler, app => 1 );</source><br />
<br />
First, I want to get the options, subpatterns, and request:<br />
<br />
<source lang="perl"># handles menu nav pages<br />
sub nav_handler {<br />
my ( $opts, $cat ) = shift;<br />
my $r = DW::Request->get;</source><br />
<br />
Remember that subpattern I made in my URL regex? I'll figure out if I'm using it or an argument here.<br />
<br />
<source lang="perl"><br />
# Check for a category like nav/read, then for a ?cat=read argument, else no category<br />
$cat ||= $r->get_args->{cat} || '';</source><br />
<br />
Here, I'm doing error checking. If I don't get back the array of menu hashes, I'm going to serve an error page that contains a translated error message. Notice how I have to define the whole path to the error message, and can't just use ".error.invalidcat".<br />
<br />
<source lang="perl"> # this function returns an array reference of menu hashes<br />
my $menu_nav = DW::Logic::MenuNav->get_menu_display( $cat )<br />
or return error_ml( '/nav.tt.error.invalidcat' );</source><br />
<br />
I'm getting some cruft that is needed by the real menu that I don't want to display on this page, so I'll go through all the menus and strip out HTML from the titles. Ideally there would be a Template Toolkit filter that could do this, but I have not found one yet; we may have to make one.<br />
<br />
<source lang="perl"> # this data doesn't need HTML in the titles, like in the real menu<br />
for my $menu ( @$menu_nav ) {<br />
for my $item ( @{ $menu->{items} } ) {<br />
$item->{text} = LJ::strip_html( $item->{text} );<br />
}<br />
}</source><br />
<br />
This is a nifty part. I can display the contents of this page either as HTML or as JSON (which is made for machine parsing), with very very little additional code.<br />
<br />
The first one is how to return something as JSON. I just print out the object conversion to the request and return OK.<br />
<br />
<source lang="perl"> # display according to the format<br />
my $format = $opts->format;<br />
if ( $format eq 'json' ) {<br />
# this prints out the menu navigation as JSON and returns<br />
$r->print( JSON::objToJson( $menu_nav ) );<br />
return $r->OK;</source><br />
<br />
The HTML format takes a bit more work. We want to pass in more variables to the template and return the rendered template. <br />
<br />
Here we go preparing the variables:<br />
<br />
<source lang="perl"> } elsif ( $format eq 'html' ) {<br />
# these variables will get passed to the template<br />
my $vars = {<br />
menu_nav => $menu_nav,<br />
cat => $cat,<br />
};<br />
<br />
$vars->{cat_title} = $menu_nav->[0]->{title} if $cat;</source><br />
<br />
And this is the call to render our template (more on making that next):<br />
<br />
<source lang="perl"> # Now we tell it what template to render and pass in our variables<br />
return DW::Template->render_template( 'nav.tt', $vars );</source><br />
<br />
If my format isn't HTML or JSON, we throw a 404.<br />
<br />
<source lang="perl"> } else { <br />
# return 404 for an unknown format<br />
return $r->NOT_FOUND;<br />
}<br />
}<br />
<br />
1;</source><br />
<br />
And that's the handler!<br />
<br />
=== Creating a template ===<br />
<br />
The template will be placed somewhere logical in <tt>$LJHOME/views</tt>. This one will be <tt>[http://hg.dwscoalition.org/dw-free/file/tip/views/nav.tt nav.tt]</tt>. Currently, translation strings will go in <tt>nav.tt.text</tt>.<br />
<br />
This section is a comment:<br />
<br />
<source lang="html4strict"><br />
[%# nav.tt<br />
<br />
Page that shows the sub-level navigation links given the top-level navigation header<br />
<br />
%]</source><br />
<br />
This section sets the title of the page. If we have a category, it will be the category title. Otherwise, it will use the ML translation of the title for the page.<br />
<br />
<source lang="html4strict">[%- IF cat; sections.title = cat_title; ELSE; sections.title = '.title' | ml; END -%]</source><br />
<br />
This section creates the menu sections, applying code for each menu and each item in each menu. If this isn't only displaying a category, it makes the title. You can see the [http://template-toolkit.org/docs/manual/Filters.html#section_html html filter] being used in places like <tt>[% menu.title | html %]</tt> to ensure that all HTML is safe, and the [http://template-toolkit.org/docs/manual/Filters.html#section_url url filter] being used in <tt>[% menu_item.url | url %]</tt> to make sure that URL will be validly encoded.<br />
<br />
<source lang="html4strict">[% FOREACH menu = menu_nav %]<br />
[% IF NOT cat %]<h2 class="[% menu.name %]">[% menu.title | html %]</h2>[% END %]<br />
<ul><br />
[% FOREACH menu_item = menu.items %]<br />
<li><a href="[% menu_item.url | url %]">[% menu_item.text | html %]</a></li><br />
[% END %]<br />
</ul><br />
[% END %]<br />
</source><br />
<br />
That template is all that's required to render the HTML for the nav page! You can see it [http://www.dreamwidth.org/nav live on Dreamwidth].<br />
<br />
== Standard tricks ==<br />
<br />
=== Inserting variables into translation strings ===<br />
<br />
The example above sneaked in the way to use the translation system from within templates, by doing:<br />
<br />
<source lang="html4strict">[%- IF cat; sections.title = cat_title; ELSE; sections.title = '.title' | ml; END -%]</source><br />
<br />
But what if you needed to insert something, such as the sitename or a username, into a string? Here's a fragment that does the latter, drawn from [http://hg.dwscoalition.org/dw-free/file/tip/views/misc/pubkey.tt views/misc/pubkey.tt]:<br />
<br />
<source lang="html4strict">[% '.label' | ml(user = u.ljuser_display) %]</source><br />
<br />
=== Including a file with a temporary ML scope change ===<br />
<br />
<source lang="html4strict"><br />
[% scope = dw.ml_scope( ); CALL dw.ml_scope( '/stats/site.tt' );<br />
INCLUDE stats/site.tt; CALL dw.ml_scope( scope ); %]<br />
</source><br />
<br />
=== Specifying needed CSS/JS files ===<br />
<br />
<source lang="html4strict"><br />
[%- CALL dw.set_active_resource_group( 'jquery' ) -%]<br />
[%- dw.need_res( 'stc/kitten.css' ) -%]<br />
[%- dw.need_res( 'js/ponies.js' ) -%]<br />
[%- dw.need_res( ( group => 'jquery' ), 'js/sparkle.js' ) -%]<br />
</source><br />
<br />
Note: ignore the first and last if your page doesn't use jQuery.<br />
<br />
For CSS, remember to used the [http://www.dreamwidth.org/dev/classes standardized classes] as much as possible.<br />
<br />
== References ==<br />
<br />
* [http://template-toolkit.org/docs/index.html Template Toolkit Documentation]<br />
* [http://docs.dreamwidth.net/DW/Request.html DW::Request API Documentation]<br />
* [http://docs.dreamwidth.net/DW/Routing.html DW::Routing API Documentation]<br />
* [http://docs.dreamwidth.net/DW/Template.html DW::Template API Documentation]<br />
* [http://docs.dreamwidth.net/DW/Template/Plugin.html Plugin Documentation]<br />
* [http://docs.dreamwidth.net/DW/Template/Filters.html Filter Documentation]<br />
<br />
=== Code Modules ===<br />
<br />
* [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Controller.pm DW::Controller]<br />
* [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Request.pm DW::Request]<br />
* [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Routing.pm DW::Routing]<br />
* [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Template.pm DW::Template]<br />
* [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Template/Plugin.pm DW::Template::Plugin]<br />
* [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Template/Filters.pm DW::Template::Filters]<br />
* [http://hg.dwscoalition.org/dw-free/file/tip/cgi-bin/DW/Template/Plugin/FormHTML.pm DW::Template::Plugin::FormHTML]<br />
<br />
[[Category: Development]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/Journalspace_PathUser:Exor674/Journalspace Path2012-02-01T01:08:10Z<p>Exor674: </p>
<hr />
<div>==Apache::LiveJournnal::trans==<br />
<br />
==$determine_view inline sub in ::trans==<br />
<br />
* Check for a [[Routing]] call in the user role<br />
<br />
If one exists, we do that and are done<br />
<br />
* Failing that..<br />
<br />
There are some special cases here ( __setdomsess, redirect journals ), but otherwise this determines the view and prepares to call top the next step.<br />
<br />
==$journal_view inline sub in ::trans==<br />
<br />
* Check if we should show the adult content interstial ( if so show it and we are done! )<br />
<br />
* /info is a redirect to full profile ( we're done )<br />
<br />
* profile is ( currently ) a call into BML ( we're done )<br />
<br />
* update is a redirect to the update page ( we're done ) NTS: move this to a routing call!<br />
<br />
* Data handlers ( we're done )<br />
<br />
====customview====<br />
<br />
====otherwise (Apache::LiveJournal::journal_content)====<br />
<br />
This is one of the main meats of the operation!<br />
<br />
* Handle robots.txt ( we're done )<br />
<br />
* Handle auth=digest ( we're done unless the user is already authenticated )<br />
<br />
* handle failed cookies, or stuff. ( we're done if cookies have failed )<br />
<br />
* Set up to call into make_journal<br />
<br />
===LJ::make_journal ( in LJ/User.pm ) ===<br />
<br />
* If user is going down the s1 path ( THIS SHOULD NEVER HAPPEN ) blow up ( we're done )<br />
<br />
-- determine_viewing_style<br />
<br />
* Determine if we are doing stlye=site or style=light<br />
<br />
* Check our fallback setting to see if we should do s2 or bml ( fallback by default is bml )<br />
<br />
* If the view is a s2/siteviews only view, set our fallback to s2<br />
<br />
* If the fallback is bml, set handle_with_bml_ref and bail back to *Apache::LiveJournal::journal_content*<br />
<br />
* If are fallback is s2, set the styleid to 'siteviews' ( special style ID )<br />
<br />
* Handle /security/ index page ( this is done with handle_with_siteviews right now, should be fixed not to, uh.. FIXME! ) [ render a TT template and bail out to Apache::LiveJournal::journal_content ]<br />
<br />
* Handle some error states [Bail back to Apache::LiveJournal::journal_content, through $security_err]<br />
<br />
* Check for valid identity views if we are an identity account [ Bail out through $error if we aren't ]<br />
<br />
* Handle feeds [ LJ::Feed::make_feed and bail ]<br />
<br />
* If **somehow** we are still going down the S1 path, well. blow up { this should NOT happen }<br />
<br />
* If we aren't doing handle_with_bml_ref, LJ::S2::make_journal ( we can bail from here and continue! )<br />
<br />
* If we are handle_with_bml_ref, and going to an icons or tags page ( pages that can be requested to be in BML from the style, and have no "BML" bersion ) OR doing the s2 fallback -- do siteviews instead.<br />
<br />
* Otherwise just bail!<br />
<br />
====LJ::S2::make_journal====<br />
<br />
* handle stylesheet special case ( we're done, back to Apache::LiveJournal::journal_content )<br />
<br />
* If we are going to render the siteviews style ( we're done, back to Apache::LiveJournal::journal_content )<br />
<br />
-- no control strip<br />
-- unset [handle_with_bml_ref]<br />
-- set [handle_with_siteviews_ref]<br />
-- prepare siteviews options, scratch space, properties, and class. ( there probably should be a own page on siteviews )<br />
<br />
* Determine if we are doing an entry or reply view and don't want journalstyle entry pages ( set handle_with_bml_ref and drop back to where we came from in LJ::make_journal )<br />
<br />
* Determine if we are doing an icons view and don't want journalstyle icons pages ( set handle_with_bml_ref and drop back to where we came from in LJ::make_journal )<br />
<br />
* Check if the user caps allow journalstyle entry/reply ( if not set handle_with_bml_ref and drop back to where we came from in LJ::make_journal ) [ Do we even do thio on DW? ]<br />
<br />
* Instantiate the class.<br />
<br />
* Call into S2 ( we're done, back to Apache::LiveJournal::journal_content )<br />
<br />
===back in (Apache::LiveJournal::journal_content)===<br />
<br />
* Add extra hooks if we didn't go down the siteviews path.<br />
<br />
* If make_journal requested an internal_redirect [internal_redir], call into routing with it. ( we're done )<br />
<br />
* If make_journal requested a redirect [redir], well, redirect ( we're done )<br />
<br />
* If make_journal requested a direct return [handler_return], return that ( we're done )<br />
<br />
* If we went down the siteviews path [handle_with_siteviews], render that result in sitescheme ( we're done )<br />
<br />
* If a BML handler was requested [handle_with_bml_ref], determine which bml file we need to render and render it. ( we're done )<br />
<br />
* .. bleh a lot more stuff ( 1412 to 1521, don't want to summarize now QQ )<br />
<br />
* We're done! Success!</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/Journalspace_PathUser:Exor674/Journalspace Path2012-02-01T01:02:31Z<p>Exor674: </p>
<hr />
<div>==Apache::LiveJournnal::trans==<br />
<br />
==$determine_view inline sub in ::trans==<br />
<br />
* Check for a [[Routing]] call in the user role<br />
<br />
If one exists, we do that and are done<br />
<br />
* Failing that..<br />
<br />
There are some special cases here ( __setdomsess, redirect journals ), but otherwise this determines the view and prepares to call top the next step.<br />
<br />
==$journal_view inline sub in ::trans==<br />
<br />
* Check if we should show the adult content interstial ( if so show it and we are done! )<br />
<br />
* /info is a redirect to full profile ( we're done )<br />
<br />
* profile is ( currently ) a call into BML ( we're done )<br />
<br />
* update is a redirect to the update page ( we're done ) NTS: move this to a routing call!<br />
<br />
* Data handlers ( we're done )<br />
<br />
====customview====<br />
<br />
====otherwise (Apache::LiveJournal::journal_content)====<br />
<br />
This is one of the main meats of the operation!<br />
<br />
* Handle robots.txt ( we're done )<br />
<br />
* Handle auth=digest ( we're done unless the user is already authenticated )<br />
<br />
* handle failed cookies, or stuff. ( we're done if cookies have failed )<br />
<br />
* Set up to call into make_journal<br />
<br />
===LJ::make_journal ( in LJ/User.pm ) ===<br />
<br />
* If user is going down the s1 path ( THIS SHOULD NEVER HAPPEN ) blow up ( we're done )<br />
<br />
-- determine_viewing_style<br />
<br />
* Determine if we are doing stlye=site or style=light<br />
<br />
* Check our fallback setting to see if we should do s2 or bml ( fallback by default is bml )<br />
<br />
* If the view is a s2/siteviews only view, set our fallback to s2<br />
<br />
* If the fallback is bml, set handle_with_bml_ref and bail back to *Apache::LiveJournal::journal_content*<br />
<br />
* If are fallback is s2, set the styleid to 'siteviews' ( special style ID )<br />
<br />
* Handle /security/ index page ( this is done with handle_with_siteviews right now, should be fixed not to, uh.. FIXME! ) [ render a TT template and bail out to Apache::LiveJournal::journal_content ]<br />
<br />
* Handle some error states [Bail back to Apache::LiveJournal::journal_content, through $security_err]<br />
<br />
* Check for valid identity views if we are an identity account [ Bail out through $error if we aren't ]<br />
<br />
* Handle feeds [ LJ::Feed::make_feed and bail ]<br />
<br />
* If **somehow** we are still going down the S1 path, well. blow up { this should NOT happen }<br />
<br />
* If we are handle_with_bml_ref, and going to an icons or tags page ( pages that can be requested to be in BML from the style, and have no "BML" bersion ) OR doing the s2 fallback -- do siteviews instead.<br />
<br />
* If we aren't doing handle_with_bml_ref, LJ::S2::make_journal!<br />
<br />
* Otherwise just bail!<br />
<br />
====LJ::S2::make_journal====<br />
<br />
<br />
===back in (Apache::LiveJournal::journal_content)===<br />
<br />
* Add extra hooks if we didn't go down the siteviews path.<br />
<br />
* If make_journal requested an internal_redirect [internal_redir], call into routing with it. ( we're done )<br />
<br />
* If make_journal requested a redirect [redir], well, redirect ( we're done )<br />
<br />
* If make_journal requested a direct return [handler_return], return that ( we're done )<br />
<br />
* If we went down the siteviews path [handle_with_siteviews], render that result in sitescheme ( we're done )<br />
<br />
* If a BML handler was requested [handle_with_bml_ref], determine which bml file we need to render and render it. ( we're done )<br />
<br />
* .. bleh a lot more stuff ( 1412 to 1521, don't want to summarize now QQ )<br />
<br />
* We're done! Success!</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/Journalspace_PathUser:Exor674/Journalspace Path2012-02-01T00:44:03Z<p>Exor674: </p>
<hr />
<div>==Apache::LiveJournnal::trans==<br />
<br />
==$determine_view inline sub in ::trans==<br />
<br />
* Check for a [[Routing]] call in the user role<br />
<br />
If one exists, we do that and are done<br />
<br />
* Failing that..<br />
<br />
There are some special cases here ( __setdomsess, redirect journals ), but otherwise this determines the view and prepares to call top the next step.<br />
<br />
==$journal_view inline sub in ::trans==<br />
<br />
* Check if we should show the adult content interstial ( if so show it and we are done! )<br />
<br />
* /info is a redirect to full profile ( we're done )<br />
<br />
* profile is ( currently ) a call into BML ( we're done )<br />
<br />
* update is a redirect to the update page ( we're done ) NTS: move this to a routing call!<br />
<br />
* Data handlers ( we're done )<br />
<br />
Customview is diffrent!!!<br />
<br />
====customview====<br />
<br />
====otherwise (Apache::LiveJournal::journal_content)====<br />
<br />
This is one of the main meats of the operation!<br />
<br />
* Handle robots.txt ( we're done )<br />
<br />
* Handle auth=digest ( we're done unless the user is already authenticated )<br />
<br />
* handle failed cookies, or stuff. ( we're done if cookies have failed )<br />
<br />
* Set up to call into make_journal<br />
<br />
===LJ::make_journal===<br />
<br />
===back in (Apache::LiveJournal::journal_content)===<br />
<br />
* Add extra hooks if we didn't go down the siteviews path.<br />
<br />
* If make_journal requested an internal_redirect [internal_redir], call into routing with it. ( we're done )<br />
<br />
* If make_journal requested a redirect [redir], well, redirect ( we're done )<br />
<br />
* If make_journal requested a direct return [handler_return], return that ( we're done )<br />
<br />
* If we went down the siteviews path, render that result in sitescheme ( we're done )<br />
<br />
* If a BML handler was requested, determine which bml file we need to render and render it. ( we're done )<br />
<br />
* .. bleh a lot more stuff ( 1412 to 1521, don't want to summarize now QQ )</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/Journalspace_PathUser:Exor674/Journalspace Path2012-02-01T00:39:18Z<p>Exor674: Created page with "==Apache::LiveJournnal::trans== ==$determine_view inline sub in ::trans== * Check for a Routing call in the user role If one exists, we do that and are done * Failing tha..."</p>
<hr />
<div>==Apache::LiveJournnal::trans==<br />
<br />
==$determine_view inline sub in ::trans==<br />
<br />
* Check for a [[Routing]] call in the user role<br />
<br />
If one exists, we do that and are done<br />
<br />
* Failing that..<br />
<br />
There are some special cases here ( __setdomsess, redirect journals ), but otherwise this determines the view and prepares to call top the next step.<br />
<br />
==$journal_view inline sub in ::trans==<br />
<br />
* Check if we should show the adult content interstial ( if so show it and we are done! )<br />
<br />
* /info is a redirect to full profile ( we're done )<br />
<br />
* profile is ( currently ) a call into BML ( we're done )<br />
<br />
* update is a redirect to the update page ( we're done ) NTS: move this to a routing call!<br />
<br />
* Data handlers ( we're done )<br />
<br />
Customview is diffrent!!!<br />
<br />
===customview===<br />
<br />
===otherwise (Apache::LiveJournal::journal_content)===<br />
<br />
This is one of the main meats of the operation!<br />
<br />
* Handle robots.txt<br />
<br />
* Handle auth=digest<br />
<br />
* handle failed cookies, or stuff.<br />
<br />
* Set up to call into make_journal<br />
<br />
===LJ::make_journal===<br />
<br />
===back in (Apache::LiveJournal::journal_content)===<br />
<br />
* if</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/NotesUser:Exor674/Notes2012-02-01T00:28:36Z<p>Exor674: </p>
<hr />
<div>* [[Template:Database Relationship]]<br />
* [[:Category:Database Relationships]]<br />
* [[User:Exor674/NewTable]]<br />
* [[User:Exor674/OAuth]]<br />
* [[User:Exor674/Git workflow]]<br />
* [[User:Exor674/Journalspace Path]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Database_Table:_usermsgpropDatabase Table: usermsgprop2011-12-23T00:33:27Z<p>Exor674: </p>
<hr />
<div>{{Database Table|name=usermsgprop|table type=clustered|need desc=1|repo=dw-free}}<br />
The properties the belong in this table are listed at [[Proplists/usermsgprop]].<br />
<br />
= Definition =<br />
<source lang="sql"><br />
CREATE TABLE usermsgprop (<br />
journalid INT UNSIGNED NOT NULL,<br />
msgid INT UNSIGNED NOT NULL,<br />
propid SMALLINT UNSIGNED NOT NULL,<br />
propval VARCHAR(255) NOT NULL,<br />
<br />
PRIMARY KEY (journalid,msgid,propid)<br />
)<br />
</source><br />
{{Database Table Footer}}</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/Git_workflowUser:Exor674/Git workflow2011-12-21T08:19:45Z<p>Exor674: </p>
<hr />
<div>{{Warn|text=This is a WIP, ignore this}}<br />
<br />
===Creating your fork===<br />
<br />
<source><br />
</source><br />
<br />
===Starting a feature===<br />
<br />
<source><br />
git flow feature start [name]<br />
</source><br />
<br />
===Pushing feature to your branch===<br />
<br />
<source><br />
git push origin feature/<name><br />
</source><br />
<br />
===Creating a pull request===<br />
<br />
===Updating your code===</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/Git_workflowUser:Exor674/Git workflow2011-12-21T08:19:12Z<p>Exor674: </p>
<hr />
<div>{{Warn|This is a WIP, ignore this}}<br />
<br />
===Creating your fork===<br />
<br />
<source><br />
</source><br />
<br />
===Starting a feature===<br />
<br />
<source><br />
git flow feature start [name]<br />
</source><br />
<br />
===Pushing feature to your branch===<br />
<br />
<source><br />
git push origin feature/<name><br />
</source><br />
<br />
===Creating a pull request===<br />
<br />
===Updating your code===</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/NotesUser:Exor674/Notes2011-12-21T08:16:45Z<p>Exor674: </p>
<hr />
<div>* [[Template:Database Relationship]]<br />
* [[:Category:Database Relationships]]<br />
* [[User:Exor674/NewTable]]<br />
* [[User:Exor674/OAuth]]<br />
* [[User:Exor674/Git workflow]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/Git_workflowUser:Exor674/Git workflow2011-12-21T08:16:30Z<p>Exor674: </p>
<hr />
<div>{{Warn|This is a WIP, ignore this}}<br />
<br />
===Creating your fork===<br />
<br />
<source><br />
</source><br />
<br />
===Starting a feature===<br />
<br />
<soruce><br />
git flow feature start [name]<br />
</source><br />
<br />
===Pushing feature to your branch===<br />
<br />
<source><br />
git push origin feature/<name><br />
</source><br />
<br />
===Creating a pull request===<br />
<br />
===Updating your code===</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/Git_workflowUser:Exor674/Git workflow2011-12-21T08:16:15Z<p>Exor674: </p>
<hr />
<div>{{warning|This is a WIP, ignore this}}<br />
<br />
===Creating your fork===<br />
<br />
<source><br />
</source><br />
<br />
===Starting a feature===<br />
<br />
<soruce><br />
git flow feature start [name]<br />
</source><br />
<br />
===Pushing feature to your branch===<br />
<br />
<source><br />
git push origin feature/<name><br />
</source><br />
<br />
===Creating a pull request===<br />
<br />
===Updating your code===</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/Git_workflowUser:Exor674/Git workflow2011-12-21T08:16:02Z<p>Exor674: Created page with "{{warning|This is a WIP, ignore this}} ===Creating your fork=== <source> </source> ===Starting a feature=== <soruce> git flow feature start <name> </source> ===Pushing featu..."</p>
<hr />
<div>{{warning|This is a WIP, ignore this}}<br />
<br />
===Creating your fork===<br />
<br />
<source><br />
</source><br />
<br />
===Starting a feature===<br />
<br />
<soruce><br />
git flow feature start <name><br />
</source><br />
<br />
===Pushing feature to your branch===<br />
<br />
<source><br />
git push origin feature/<name><br />
</source><br />
<br />
===Creating a pull request===<br />
<br />
===Updating your code===</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/monobook.jsUser:Exor674/monobook.js2011-12-18T06:25:50Z<p>Exor674: </p>
<hr />
<div>importScriptURI('http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js');<br />
importScript('User:Exor674/morebits.js')</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/monobook.jsUser:Exor674/monobook.js2011-12-18T06:24:45Z<p>Exor674: </p>
<hr />
<div>import('http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js');</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/monobook.jsUser:Exor674/monobook.js2011-12-18T06:24:16Z<p>Exor674: Created page with "importScript('http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js');"</p>
<hr />
<div>importScript('http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js');</div>Exor674//wiki.dreamwidth.net/wiki/index.php/User:Exor674/morebits.jsUser:Exor674/morebits.js2011-12-18T06:19:01Z<p>Exor674: Created page with "// <nowiki> /** * morebits.js * =========== * A library full of lots of goodness for user scripts on Wikipedia. * (It should work on other MediaWiki wikis as well, despite so..."</p>
<hr />
<div>// <nowiki><br />
/**<br />
* morebits.js<br />
* ===========<br />
* A library full of lots of goodness for user scripts on Wikipedia.<br />
* (It should work on other MediaWiki wikis as well, despite some Wikipedia-specific object naming.)<br />
*<br />
* The highlights include:<br />
* - QuickForm class - generates quick HTML forms on the fly<br />
* - Wikipedia.api class - makes calls to the Wikipedia API (or the API of any MediaWiki wiki)<br />
* - Wikipedia.page class - modifies pages on the wiki (edit, revert, delete, etc.)<br />
* - MediaWiki class - contains some utilities for dealing with wikitext<br />
* - Status class - a rough-and-ready status message displayer, used by the Wikipedia classes<br />
* - SimpleWindow class - a wrapper for jQuery UI Dialog with a custom look and extra features<br />
*<br />
* Dependencies:<br />
* - The whole thing relies on jQuery. But most wikis should provide this by default.<br />
* - QuickForm, SimpleWindow, Status, and the portlet stuff rely on the "morebits.css" file for their styling.<br />
* - SimpleWindow relies on jquery UI Dialog (ResourceLoader module name 'jquery.ui.dialog').<br />
* - QuickForm tooltips rely on Tipsy (ResourceLoader module name 'jquery.tipsy').<br />
* For external installations, Tipsy is available at [http://onehackoranother.com/projects/jquery/tipsy].<br />
* - To create a gadget based on morebits.js, use this syntax in MediaWiki:Gadgets-definition:<br />
* * GadgetName[ResourceLoader|dependencies=jquery.ui.dialog,jquery.tipsy]|morebits.js|morebits.css|GadgetName.js<br />
*<br />
* Most of the stuff here doesn't work on IE < 9. It is your script's responsibility to enforce this.<br />
*<br />
* This library is maintained by the maintainers of Twinkle.<br />
* For queries, suggestions, help, etc., head to [[Wikipedia talk:Twinkle]] on English Wikipedia [http://en.wikipedia.org].<br />
* The latest development source is available at [https://github.com/azatoth/twinkle/blob/master/morebits.js].<br />
*/<br />
<br />
<br />
/**<br />
* **************** userIsInGroup(), userIsAnon() ****************<br />
* Simple helper functions to see what groups a user might belong<br />
*/<br />
<br />
function userIsInGroup( group ) {<br />
return $.inArray(group, mw.config.get( 'wgUserGroups' )) !== -1;<br />
}<br />
function userIsAnon() {<br />
return mw.config.get( 'wgUserGroups' ).length === 1;<br />
}<br />
<br />
<br />
/**<br />
* **************** Cookies ****************<br />
*/<br />
<br />
var Cookies = {<br />
/*<br />
* Creates an cookie with the name and value pair. expiry is optional or null and defaults<br />
* to browser standard (in seconds), path is optional and defaults to "/"<br />
* throws error if the cookie already exists.<br />
*/<br />
create: function( name, value, max_age, path ) {<br />
if( Cookies.exists( name ) ) {<br />
throw new Error( "cookie " + name + " already exists" );<br />
}<br />
Cookies.set( name, value, max_age, path );<br />
},<br />
/*<br />
* Sets an cookie with the name and value pair, overwrites any previous cookie of that name.<br />
* expiry is optional or null and defaults to browser standard (in seconds),<br />
* path is optional and defaults to /<br />
*/<br />
set: function( name, value, max_age, path ) {<br />
var cookie = name + "=" + encodeURIComponent( value );<br />
if( max_age ) {<br />
cookie += "; max-age=" + max_age;<br />
}<br />
cookie += "; path=" + path || "/";<br />
document.cookie = cookie;<br />
},<br />
/*<br />
* Retuns the cookie with the name "name", return null if no cookie found.<br />
*/<br />
read: function( name ) {<br />
var cookies = document.cookie.split(";");<br />
for( var i = 0; i < cookies.length; ++i ) {<br />
var current = cookies[i];<br />
current = current.trim();<br />
if( current.indexOf( name + "=" ) === 0 ) {<br />
return decodeURIComponent( current.substring( name.length + 1 ) );<br />
}<br />
}<br />
return null;<br />
},<br />
/*<br />
* Returns true if a cookie exists, false otherwise<br />
*/<br />
exists: function( name ) {<br />
var re = new RegExp( ";\\s*" + name + "=" );<br />
return re.test( document.cookie );<br />
},<br />
/*<br />
* Deletes the cookie named "name"<br />
*/<br />
remove: function( name ) {<br />
Cookies.set( name, '', -1 );<br />
}<br />
};<br />
<br />
<br />
/**<br />
* **************** QuickForm ****************<br />
* QuickForm is a class for creation of simple and standard forms without much <br />
* specific coding.<br />
*<br />
* Index to QuickForm element types:<br />
*<br />
* select A combo box (aka drop-down).<br />
* - Attributes: name, label, multiple, size, list, event<br />
* option An element for a combo box.<br />
* - Attributes: value, label, selected, disabled<br />
* optgroup A group of "option"s.<br />
* - Attributes: label, list<br />
* field A fieldset (aka group box).<br />
* - Attributes: name, label<br />
* checkbox A checkbox. Must use "list" parameter.<br />
* - Attributes: name, list, event<br />
* - Attributes (within list): name, label, value, checked, disabled, event, subgroup<br />
* radio A radio button. Must use "list" parameter.<br />
* - Attributes: name, list, event<br />
* - Attributes (within list): name, label, value, checked, disabled, event, subgroup<br />
* input A text box.<br />
* - Attributes: name, label, value, size, disabled, readonly, maxlength, event<br />
* dyninput A set of text boxes with "Remove" buttons and an "Add" button.<br />
* - Attributes: name, label, min, max, sublabel, value, size, maxlength, event<br />
* hidden An invisible form field.<br />
* - Attributes: name, value<br />
* header A level 5 header.<br />
* - Attributes: label<br />
* div A generic placeholder element or label.<br />
* - Attributes: name, label<br />
* submit A submit button. SimpleWindow moves these to the footer of the dialog.<br />
* - Attributes: name, label, disabled<br />
* button A generic button.<br />
* - Attributes: name, label, disabled, event<br />
* textarea A big, multi-line text box.<br />
* - Attributes: name, label, value, cols, rows, disabled, readonly<br />
*<br />
* Global attributes: id, tooltip, extra, adminonly<br />
*/<br />
<br />
var QuickForm = function QuickForm( event, eventType ) {<br />
this.root = new QuickForm.element( { type: 'form', event: event, eventType:eventType } );<br />
};<br />
<br />
QuickForm.prototype.render = function QuickFormRender() {<br />
var ret = this.root.render();<br />
ret.names = {};<br />
return ret;<br />
};<br />
<br />
QuickForm.prototype.append = function QuickFormAppend( data ) {<br />
return this.root.append( data );<br />
};<br />
<br />
QuickForm.element = function QuickFormElement( data ) {<br />
this.data = data;<br />
this.childs = [];<br />
this.id = QuickForm.element.id++;<br />
};<br />
<br />
QuickForm.element.id = 0;<br />
<br />
QuickForm.element.prototype.append = function QuickFormElementAppend( data ) {<br />
var child;<br />
if( data instanceof QuickForm.element ) {<br />
child = data;<br />
} else {<br />
child = new QuickForm.element( data );<br />
}<br />
this.childs.push( child );<br />
return child;<br />
};<br />
<br />
QuickForm.element.prototype.render = function QuickFormElementRender() {<br />
var currentNode = this.compute( this.data );<br />
<br />
for( var i = 0; i < this.childs.length; ++i ) {<br />
currentNode[1].appendChild( this.childs[i].render() );<br />
}<br />
return currentNode[0];<br />
};<br />
<br />
QuickForm.element.prototype.compute = function QuickFormElementCompute( data, in_id ) {<br />
var node;<br />
var childContainder = null;<br />
var label;<br />
var id = ( in_id ? in_id + '_' : '' ) + 'node_' + this.id;<br />
if( data.adminonly && !userIsInGroup( 'sysop' ) ) {<br />
// hell hack alpha<br />
data.type = 'hidden';<br />
}<br />
<br />
var i, current, subnode;<br />
switch( data.type ) {<br />
case 'form':<br />
node = document.createElement( 'form' );<br />
node.setAttribute( 'name', 'id' );<br />
node.className = "quickform";<br />
node.setAttribute( 'action', 'javascript:void(0);');<br />
if( data.event ) {<br />
node.addEventListener( data.eventType || 'submit', data.event , false );<br />
}<br />
break;<br />
case 'select':<br />
node = document.createElement( 'div' );<br />
<br />
node.setAttribute( 'id', 'div_' + id );<br />
if( data.label ) {<br />
label = node.appendChild( document.createElement( 'label' ) );<br />
label.setAttribute( 'for', id );<br />
label.appendChild( document.createTextNode( data.label ) );<br />
}<br />
var select = node.appendChild( document.createElement( 'select' ) );<br />
if( data.event ) {<br />
select.addEventListener( 'change', data.event, false );<br />
}<br />
if( data.multiple ) {<br />
select.setAttribute( 'multiple', 'multiple' );<br />
}<br />
if( data.size ) {<br />
select.setAttribute( 'size', data.size );<br />
}<br />
select.setAttribute( 'name', data.name );<br />
<br />
if( data.list ) {<br />
for( i = 0; i < data.list.length; ++i ) {<br />
<br />
current = data.list[i];<br />
<br />
if( current.list ) {<br />
current.type = 'optgroup';<br />
} else {<br />
current.type = 'option';<br />
}<br />
<br />
subnode = this.compute( current );<br />
select.appendChild( subnode[0] );<br />
}<br />
}<br />
childContainder = select;<br />
break;<br />
case 'option':<br />
node = document.createElement( 'option' );<br />
node.values = data.value;<br />
node.setAttribute( 'value', data.value );<br />
if( data.selected ) {<br />
node.setAttribute( 'selected', 'selected' );<br />
}<br />
if( data.disabled ) {<br />
node.setAttribute( 'disabled', 'disabled' );<br />
}<br />
node.setAttribute( 'label', data.label );<br />
node.appendChild( document.createTextNode( data.label ) );<br />
break;<br />
case 'optgroup':<br />
node = document.createElement( 'optgroup' );<br />
node.setAttribute( 'label', data.label );<br />
<br />
if( data.list ) {<br />
for( i = 0; i < data.list.length; ++i ) {<br />
<br />
current = data.list[i];<br />
current.type = 'option'; //must be options here<br />
<br />
subnode = this.compute( current );<br />
node.appendChild( subnode[0] );<br />
}<br />
}<br />
break;<br />
case 'field':<br />
node = document.createElement( 'fieldset' );<br />
label = node.appendChild( document.createElement( 'legend' ) );<br />
label.appendChild( document.createTextNode( data.label ) );<br />
if( data.name ) {<br />
node.setAttribute( 'name', data.name );<br />
}<br />
break;<br />
case 'checkbox':<br />
case 'radio':<br />
node = document.createElement( 'div' );<br />
if( data.list ) {<br />
for( i = 0; i < data.list.length; ++i ) {<br />
var cur_id = id + '_' + i;<br />
current = data.list[i];<br />
var cur_div;<br />
if( current.type === 'header' ) {<br />
// inline hack<br />
cur_div = node.appendChild( document.createElement( 'h6' ) );<br />
cur_div.appendChild( document.createTextNode( current.label ) );<br />
if( current.tooltip ) {<br />
QuickForm.element.generateTooltip( cur_div , current );<br />
}<br />
continue;<br />
}<br />
cur_div = node.appendChild( document.createElement( 'div' ) );<br />
subnode = cur_div.appendChild( document.createElement( 'input' ) );<br />
subnode.values = current.value;<br />
subnode.setAttribute( 'value', current.value );<br />
subnode.setAttribute( 'name', current.name || data.name );<br />
subnode.setAttribute( 'type', data.type );<br />
subnode.setAttribute( 'id', cur_id );<br />
<br />
if( current.checked ) {<br />
subnode.setAttribute( 'checked', 'checked' );<br />
}<br />
if( current.disabled ) {<br />
subnode.setAttribute( 'disabled', 'disabled' );<br />
}<br />
if( data.event ) {<br />
subnode.addEventListener( 'change', data.event, false );<br />
} else if ( current.event ) {<br />
subnode.addEventListener( 'change', current.event, true );<br />
}<br />
label = cur_div.appendChild( document.createElement( 'label' ) );<br />
label.appendChild( document.createTextNode( current.label ) );<br />
label.setAttribute( 'for', cur_id );<br />
if( current.tooltip ) {<br />
QuickForm.element.generateTooltip( label, current );<br />
}<br />
var event;<br />
if( current.subgroup ) {<br />
var tmpgroup = current.subgroup;<br />
if( ! tmpgroup.type ) {<br />
tmpgroup.type = data.type;<br />
}<br />
tmpgroup.name = (current.name || data.name) + '.' + tmpgroup.name;<br />
<br />
var subgroup =this.compute( current.subgroup, cur_id )[0];<br />
subgroup.style.marginLeft = '3em';<br />
subnode.subgroup = subgroup;<br />
subnode.shown = false;<br />
<br />
event = function(e) {<br />
if( e.target.checked ) {<br />
e.target.parentNode.appendChild( e.target.subgroup );<br />
if( e.target.type === 'radio' ) {<br />
var name = e.target.name;<br />
if( typeof( e.target.form.names[name] ) !== 'undefined' ) {<br />
e.target.form.names[name].parentNode.removeChild( e.target.form.names[name].subgroup );<br />
}<br />
e.target.form.names[name] = e.target;<br />
}<br />
} else {<br />
e.target.parentNode.removeChild( e.target.subgroup );<br />
}<br />
};<br />
subnode.addEventListener( 'change', event, true );<br />
if( current.checked ) {<br />
subnode.parentNode.appendChild( subgroup );<br />
}<br />
} else if( data.type === 'radio' ) {<br />
event = function(e) {<br />
if( e.target.checked ) {<br />
var name = e.target.name;<br />
if( typeof( e.target.form.names[name] ) !== 'undefined' ) {<br />
e.target.form.names[name].parentNode.removeChild( e.target.form.names[name].subgroup );<br />
}<br />
delete e.target.form.names[name];<br />
} <br />
};<br />
subnode.addEventListener( 'change', event, true );<br />
}<br />
}<br />
}<br />
break;<br />
case 'input':<br />
node = document.createElement( 'div' );<br />
<br />
if( data.label ) {<br />
label = node.appendChild( document.createElement( 'label' ) );<br />
label.appendChild( document.createTextNode( data.label ) );<br />
label.setAttribute( 'for', id );<br />
}<br />
<br />
subnode = node.appendChild( document.createElement( 'input' ) );<br />
if( data.value ) {<br />
subnode.setAttribute( 'value', data.value );<br />
}<br />
subnode.setAttribute( 'name', data.name );<br />
subnode.setAttribute( 'type', 'text' );<br />
if( data.size ) {<br />
subnode.setAttribute( 'size', data.size );<br />
}<br />
if( data.disabled ) {<br />
subnode.setAttribute( 'disabled', 'disabled' );<br />
}<br />
if( data.readonly ) {<br />
subnode.setAttribute( 'readonly', 'readonly' );<br />
}<br />
if( data.maxlength ) {<br />
subnode.setAttribute( 'maxlength', data.maxlength );<br />
}<br />
if( data.event ) {<br />
subnode.addEventListener( 'keyup', data.event, false );<br />
}<br />
break;<br />
case 'dyninput':<br />
var min = data.min || 1;<br />
var max = data.max || Infinity;<br />
<br />
node = document.createElement( 'div' );<br />
<br />
label = node.appendChild( document.createElement( 'h5' ) );<br />
label.appendChild( document.createTextNode( data.label ) );<br />
<br />
var listNode = node.appendChild( document.createElement( 'div' ) );<br />
<br />
var more = this.compute( {<br />
type: 'button',<br />
label: 'more',<br />
disabled: min >= max,<br />
event: function(e) {<br />
var area = e.target.area;<br />
var new_node = new QuickForm.element( e.target.sublist );<br />
e.target.area.appendChild( new_node.render() );<br />
<br />
if( ++e.target.counter >= e.target.max ) {<br />
e.target.setAttribute( 'disabled', 'disabled' );<br />
}<br />
e.stopPropagation();<br />
}<br />
} );<br />
<br />
node.appendChild( more[0] );<br />
var moreButton = more[1];<br />
<br />
var sublist = {<br />
type: '_dyninput_element',<br />
label: data.sublabel || data.label,<br />
name: data.name,<br />
value: data.value,<br />
size: data.size,<br />
remove: false,<br />
maxlength: data.maxlength,<br />
event: data.event<br />
};<br />
<br />
for( i = 0; i < min; ++i ) {<br />
var elem = new QuickForm.element( sublist );<br />
listNode.appendChild( elem.render() );<br />
}<br />
sublist.remove = true;<br />
sublist.morebutton = moreButton;<br />
sublist.listnode = listNode;<br />
<br />
moreButton.sublist = sublist;<br />
moreButton.area = listNode;<br />
moreButton.max = max - min;<br />
moreButton.counter = 0;<br />
break;<br />
case '_dyninput_element': // Private, similar to normal input<br />
node = document.createElement( 'div' );<br />
<br />
if( data.label ) {<br />
label = node.appendChild( document.createElement( 'label' ) );<br />
label.appendChild( document.createTextNode( data.label ) );<br />
label.setAttribute( 'for', id );<br />
}<br />
<br />
subnode = node.appendChild( document.createElement( 'input' ) );<br />
if( data.value ) {<br />
subnode.setAttribute( 'value', data.value );<br />
}<br />
subnode.setAttribute( 'name', data.name );<br />
subnode.setAttribute( 'type', 'text' );<br />
if( data.size ) {<br />
subnode.setAttribute( 'size', data.size );<br />
}<br />
if( data.maxlength ) {<br />
subnode.setAttribute( 'maxlength', data.maxlength );<br />
}<br />
if( data.event ) {<br />
subnode.addEventListener( 'keyup', data.event, false );<br />
}<br />
if( data.remove ) {<br />
var remove = this.compute( {<br />
type: 'button',<br />
label: 'remove',<br />
event: function(e) {<br />
var list = e.target.listnode;<br />
var node = e.target.inputnode;<br />
var more = e.target.morebutton;<br />
<br />
list.removeChild( node );<br />
--more.counter;<br />
more.removeAttribute( 'disabled' );<br />
e.stopPropagation();<br />
}<br />
} );<br />
node.appendChild( remove[0] );<br />
var removeButton = remove[1];<br />
removeButton.inputnode = node;<br />
removeButton.listnode = data.listnode;<br />
removeButton.morebutton = data.morebutton;<br />
}<br />
break;<br />
case 'hidden':<br />
node = document.createElement( 'input' );<br />
node.setAttribute( 'type', 'hidden' );<br />
node.values = data.value;<br />
node.setAttribute( 'value', data.value );<br />
node.setAttribute( 'name', data.name );<br />
break;<br />
case 'header':<br />
node = document.createElement( 'h5' );<br />
node.appendChild( document.createTextNode( data.label ) );<br />
break;<br />
case 'div':<br />
node = document.createElement( 'div' );<br />
if (data.name) {<br />
node.setAttribute( 'name', data.name );<br />
}<br />
if (data.label) {<br />
if ( !( data.label instanceof Array ) ) {<br />
data.label = [ data.label ];<br />
}<br />
var result = document.createElement( 'span' );<br />
result.className = 'quickformDescription';<br />
for( i = 0; i < data.label.length; ++i ) {<br />
if( typeof(data.label[i]) === 'string' ) {<br />
result.appendChild( document.createTextNode( data.label[i] ) );<br />
} else if( data.label[i] instanceof Element ) {<br />
result.appendChild( data.label[i] );<br />
}<br />
}<br />
node.appendChild( result );<br />
}<br />
break;<br />
case 'submit':<br />
node = document.createElement( 'span' );<br />
childContainder = node.appendChild(document.createElement( 'input' ));<br />
childContainder.setAttribute( 'type', 'submit' );<br />
if( data.label ) {<br />
childContainder.setAttribute( 'value', data.label );<br />
}<br />
childContainder.setAttribute( 'name', data.name || 'submit' );<br />
if( data.disabled ) {<br />
childContainder.setAttribute( 'disabled', 'disabled' );<br />
}<br />
break;<br />
case 'button':<br />
node = document.createElement( 'span' );<br />
childContainder = node.appendChild(document.createElement( 'input' ));<br />
childContainder.setAttribute( 'type', 'button' );<br />
if( data.label ) {<br />
childContainder.setAttribute( 'value', data.label );<br />
}<br />
childContainder.setAttribute( 'name', data.name );<br />
if( data.disabled ) {<br />
childContainder.setAttribute( 'disabled', 'disabled' );<br />
}<br />
if( data.event ) {<br />
childContainder.addEventListener( 'click', data.event, false );<br />
}<br />
break;<br />
case 'textarea':<br />
node = document.createElement( 'div' );<br />
if( data.label ) {<br />
label = node.appendChild( document.createElement( 'h5' ) );<br />
label.appendChild( document.createTextNode( data.label ) );<br />
label.setAttribute( 'for', id );<br />
}<br />
subnode = node.appendChild( document.createElement( 'textarea' ) );<br />
subnode.setAttribute( 'name', data.name );<br />
if( data.cols ) {<br />
subnode.setAttribute( 'cols', data.cols );<br />
}<br />
if( data.rows ) {<br />
subnode.setAttribute( 'rows', data.rows );<br />
}<br />
if( data.disabled ) {<br />
subnode.setAttribute( 'disabled', 'disabled' );<br />
}<br />
if( data.readonly ) {<br />
subnode.setAttribute( 'readonly', 'readonly' );<br />
}<br />
if( data.value ) {<br />
subnode.value = data.value;<br />
}<br />
break;<br />
default:<br />
throw new Error("QuickForm: unknown element type " + data.type.toString());<br />
}<br />
<br />
if( !childContainder ) {<br />
childContainder = node;<br />
} <br />
if( data.tooltip ) {<br />
QuickForm.element.generateTooltip( label || node , data );<br />
}<br />
<br />
if( data.extra ) {<br />
childContainder.extra = data.extra;<br />
}<br />
childContainder.setAttribute( 'id', data.id || id );<br />
<br />
return [ node, childContainder ];<br />
};<br />
<br />
QuickForm.element.generateTooltip = function QuickFormElementGenerateTooltip( node, data ) {<br />
$('<span/>', {<br />
'class': 'ui-icon ui-icon-help ui-icon-inline morebits-tooltip'<br />
}).appendTo(node).tipsy({<br />
'fallback': data.tooltip,<br />
'fade': true,<br />
'gravity': $.fn.tipsy.autoWE,<br />
'html': true,<br />
'delayOut': 250<br />
});<br />
};<br />
<br />
<br />
/**<br />
* **************** HTMLFormElement ****************<br />
*<br />
* getChecked: <br />
* XXX Doesn't seem to work reliably across all browsers at the moment. -- see getChecked2 in twinkleunlink.js, which is better<br />
*<br />
* Returns an array containing the values of elements with the given name, that has it's<br />
* checked property set to true. (i.e. a checkbox or a radiobutton is checked), or select options<br />
* that have selected set to true. (don't try to mix selects with radio/checkboxes, please)<br />
* Type is optional and can specify if either radio or checkbox (for the event<br />
* that both checkboxes and radiobuttons have the same name.<br />
*/<br />
<br />
HTMLFormElement.prototype.getChecked = function( name, type ) {<br />
var elements = this.elements[name];<br />
if( !elements ) { <br />
// if the element doesn't exists, return null.<br />
return null;<br />
}<br />
var return_array = [];<br />
var i;<br />
if( elements instanceof HTMLSelectElement ) {<br />
var options = elements.options;<br />
for( i = 0; i < options.length; ++i ) {<br />
if( options[i].selected ) {<br />
if( options[i].values ) {<br />
return_array.push( options[i].values );<br />
} else {<br />
return_array.push( options[i].value );<br />
}<br />
<br />
}<br />
}<br />
} else if( elements instanceof HTMLInputElement ) {<br />
if( type && elements.type !== type ) {<br />
return [];<br />
} else if( elements.checked ) {<br />
return [ elements.value ];<br />
}<br />
} else {<br />
for( i = 0; i < elements.length; ++i ) {<br />
if( elements[i].checked ) {<br />
if( type && elements[i].type !== type ) {<br />
continue;<br />
}<br />
if( elements[i].values ) {<br />
return_array.push( elements[i].values );<br />
} else {<br />
return_array.push( elements[i].value );<br />
}<br />
}<br />
}<br />
}<br />
return return_array;<br />
};<br />
<br />
<br />
/**<br />
* **************** RegExp ****************<br />
*<br />
* RegExp.escape: Will escape a string to be used in a RegExp<br />
*/<br />
<br />
RegExp.escape = function( text, space_fix ) {<br />
<br />
if ( !arguments.callee.sRE ) {<br />
arguments.callee.sRE = /(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}|\\|\$|\^)/g;<br />
}<br />
<br />
text = text.replace( arguments.callee.sRE , '\\$1' );<br />
<br />
// Special Mediawiki escape, underscore/space is the same, often at lest:<br />
<br />
if( space_fix ) {<br />
text = text.replace( / |_/g, '[_ ]' );<br />
}<br />
<br />
return text;<br />
};<br />
<br />
<br />
/**<br />
* **************** Bytes ****************<br />
*/<br />
<br />
var Bytes = function( value ) {<br />
if( typeof(value) === 'string' ) {<br />
var res = /(\d+) ?(\w?)(i?)B?/.exec( value );<br />
var number = res[1];<br />
var mag = res[2];<br />
var si = res[3];<br />
<br />
if( ! number ) {<br />
this.number = 0;<br />
return;<br />
}<br />
<br />
if( !si ) {<br />
this.value = number * Math.pow( 10, Bytes.magnitudes[mag] * 3 );<br />
} else {<br />
this.value = number * Math.pow( 2, Bytes.magnitudes[mag] * 10 );<br />
}<br />
} else {<br />
this.value = value;<br />
}<br />
};<br />
<br />
Bytes.magnitudes = {<br />
'': 0,<br />
'K': 1,<br />
'M': 2,<br />
'G': 3,<br />
'T': 4,<br />
'P': 5,<br />
'E': 6,<br />
'Z': 7,<br />
'Y': 8<br />
};<br />
<br />
Bytes.rmagnitudes = {<br />
0: '',<br />
1: 'K',<br />
2: 'M',<br />
3: 'G',<br />
4: 'T',<br />
5: 'P',<br />
6: 'E',<br />
7: 'Z',<br />
8: 'Y'<br />
};<br />
<br />
Bytes.prototype.valueOf = function() {<br />
return this.value;<br />
};<br />
<br />
Bytes.prototype.toString = function( magnitude ) {<br />
var tmp = this.value;<br />
if( magnitude ) {<br />
var si = /i/.test(magnitude);<br />
var mag = magnitude.replace( /.*?(\w)i?B?.*/g, '$1' );<br />
if( si ) {<br />
tmp /= Math.pow( 2, Bytes.magnitudes[mag] * 10 );<br />
} else {<br />
tmp /= Math.pow( 10, Bytes.magnitudes[mag] * 3 );<br />
}<br />
if( parseInt( tmp, 10 ) !== tmp ) {<br />
tmp = Number( tmp ).toPrecision( 4 );<br />
}<br />
return tmp + ' ' + mag + (si?'i':'') + 'B';<br />
} else {<br />
// si per default<br />
var current = 0;<br />
while( tmp >= 1024 ) {<br />
tmp /= 1024;<br />
++current;<br />
}<br />
tmp = this.value / Math.pow( 2, current * 10 );<br />
if( parseInt( tmp, 10 ) !== tmp ) {<br />
tmp = Number( tmp ).toPrecision( 4 );<br />
}<br />
return tmp + ' ' + Bytes.rmagnitudes[current] + ( current > 0 ? 'iB' : 'B' );<br />
}<br />
};<br />
<br />
<br />
/**<br />
* **************** String ****************<br />
*/<br />
<br />
String.prototype.ltrim = function stringPrototypeLtrim( chars ) {<br />
chars = chars || "\\s";<br />
return this.replace( new RegExp("^[" + chars + "]+", "g"), "" );<br />
};<br />
<br />
String.prototype.rtrim = function stringPrototypeRtrim( chars ) {<br />
chars = chars || "\\s";<br />
return this.replace( new RegExp("[" + chars + "]+$", "g"), "" );<br />
};<br />
<br />
String.prototype.trim = function stringPrototypeTrim( chars ) {<br />
return this.rtrim(chars).ltrim(chars);<br />
};<br />
<br />
String.prototype.splitWeightedByKeys = function stringPrototypeSplitWeightedByKeys( start, end, skip ) {<br />
if( start.length !== end.length ) {<br />
throw new Error( 'start marker and end marker must be of the same length' );<br />
}<br />
var level = 0;<br />
var initial = null;<br />
var result = [];<br />
if( !( skip instanceof Array ) ) {<br />
if( typeof( skip ) === 'undefined' ) {<br />
skip = [];<br />
} else if( typeof( skip ) === 'string' ) {<br />
skip = [ skip ];<br />
} else {<br />
throw new Error( "non-applicable skip parameter" );<br />
}<br />
}<br />
for( var i = 0; i < this.length; ++i ) {<br />
for( var j = 0; j < skip.length; ++j ) {<br />
if( this.substr( i, skip[j].length ) === skip[j] ) {<br />
i += skip[j].length - 1;<br />
continue;<br />
}<br />
}<br />
if( this.substr( i, start.length ) === start ) {<br />
if( initial === null ) {<br />
initial = i;<br />
}<br />
++level;<br />
i += start.length - 1;<br />
} else if( this.substr( i, end.length ) === end ) {<br />
--level;<br />
i += end.length - 1;<br />
}<br />
if( !level && initial ) {<br />
result.push( this.substring( initial, i + 1 ) );<br />
initial = null;<br />
}<br />
}<br />
<br />
return result;<br />
};<br />
<br />
// Helper functions to change case of a string<br />
String.prototype.toUpperCaseFirstChar = function() {<br />
return this.substr( 0, 1 ).toUpperCase() + this.substr( 1 );<br />
};<br />
<br />
String.prototype.toLowerCaseFirstChar = function() {<br />
return this.substr( 0, 1 ).toLowerCase() + this.substr( 1 );<br />
};<br />
<br />
String.prototype.toUpperCaseEachWord = function( delim ) {<br />
delim = delim ? delim : ' ';<br />
return this.split( delim ).map( function(v) { return v.toUpperCaseFirstChar(); } ).join( delim );<br />
};<br />
<br />
String.prototype.toLowerCaseEachWord = function( delim ) {<br />
delim = delim ? delim : ' ';<br />
return this.split( delim ).map( function(v) { return v.toLowerCaseFirstChar(); } ).join( delim );<br />
};<br />
<br />
<br />
/**<br />
* **************** Array ****************<br />
*/<br />
<br />
Array.prototype.uniq = function arrayPrototypeUniq() {<br />
var result = [];<br />
for( var i = 0; i < this.length; ++i ) {<br />
var current = this[i];<br />
if( result.indexOf( current ) === -1 ) {<br />
result.push( current );<br />
}<br />
}<br />
return result;<br />
};<br />
<br />
Array.prototype.dups = function arrayPrototypeUniq() {<br />
var uniques = [];<br />
var result = [];<br />
for( var i = 0; i < this.length; ++i ) {<br />
var current = this[i];<br />
if( uniques.indexOf( current ) === -1 ) {<br />
uniques.push( current );<br />
} else {<br />
result.push( current );<br />
}<br />
}<br />
return result;<br />
};<br />
<br />
Array.prototype.chunk = function arrayChunk( size ) {<br />
if( typeof( size ) !== 'number' || size <= 0 ) { // pretty impossible to do anything :)<br />
return [ this ]; // we return an array consisting of this array.<br />
}<br />
var result = [];<br />
var current;<br />
for(var i = 0; i < this.length; ++i ) {<br />
if( i % size === 0 ) { // when 'i' is 0, this is always true, so we start by creating one.<br />
current = [];<br />
result.push( current );<br />
}<br />
current.push( this[i] );<br />
}<br />
return result;<br />
};<br />
<br />
<br />
/**<br />
* **************** Unbinder ****************<br />
* Used by MediaWiki.commentOutImage<br />
*/<br />
<br />
function Unbinder( string ) {<br />
if( typeof( string ) !== 'string' ) {<br />
throw new Error( "not a string" );<br />
}<br />
this.content = string;<br />
this.counter = 0;<br />
this.history = {};<br />
this.prefix = '%UNIQ::' + Math.random() + '::';<br />
this.postfix = '::UNIQ%';<br />
}<br />
<br />
Unbinder.prototype = {<br />
unbind: function UnbinderUnbind( prefix, postfix ) {<br />
var re = new RegExp( prefix + '(.*?)' + postfix, 'g' );<br />
this.content = this.content.replace( re, Unbinder.getCallback( this ) );<br />
},<br />
rebind: function UnbinderRebind() {<br />
var content = this.content;<br />
content.self = this;<br />
for( var current in this.history ) {<br />
if( this.history.hasOwnProperty( current ) ) {<br />
content = content.replace( current, this.history[current] );<br />
}<br />
}<br />
return content;<br />
},<br />
prefix: null, // %UNIQ::0.5955981644938324::<br />
postfix: null, // ::UNIQ%<br />
content: null, // string<br />
counter: null, // 0++<br />
history: null // {}<br />
};<br />
<br />
Unbinder.getCallback = function UnbinderGetCallback(self) {<br />
return function UnbinderCallback( match , a , b ) {<br />
var current = self.prefix + self.counter + self.postfix;<br />
self.history[current] = match;<br />
++self.counter;<br />
return current;<br />
};<br />
};<br />
<br />
<br />
/**<br />
* **************** clone() ****************<br />
* REMOVEME - global namespace pollution -> move to better name, or<br />
* rework the few usages using jQuery.extend<br />
*/<br />
<br />
function clone( obj, deep ) {<br />
var objectClone = new obj.constructor();<br />
for ( var property in obj ) {<br />
if ( !deep ) {<br />
objectClone[property] = obj[property];<br />
} else if ( typeof obj[property] === 'object' ) {<br />
objectClone[property] = clone( obj[property], deep );<br />
} else {<br />
objectClone[property] = obj[property];<br />
}<br />
}<br />
return objectClone;<br />
}<br />
<br />
<br />
/**<br />
* **************** Namespace ****************<br />
*/<br />
<br />
var Namespace = {<br />
MAIN: 0,<br />
TALK: 1,<br />
USER: 2,<br />
USER_TALK: 3,<br />
PROJECT: 4,<br />
PROJECT_TALK: 5,<br />
IMAGE: 6,<br />
IMAGE_TALK: 7,<br />
FILE: 6,<br />
FILE_TALK: 7,<br />
MEDIAWIKI: 8,<br />
MEDIAWIKI_TALK: 9,<br />
TEMPLATE: 10,<br />
TEMPLATE_TALK: 11,<br />
HELP: 12,<br />
HELP_TALK: 13,<br />
CATEGORY: 14,<br />
CATEGORY_TALK: 15,<br />
PORTAL: 100,<br />
PORTAL_TALK: 101,<br />
BOOK: 108,<br />
BOOK_TALK: 109,<br />
MEDIA: -2,<br />
SPECIAL: -1,<br />
<br />
"": 0,<br />
WIKIPEDIA: 4,<br />
WIKIPEDIA_TALK: 5,<br />
WP: 4,<br />
WT: 5<br />
};<br />
<br />
<br />
/**<br />
* **************** Date ****************<br />
* Helper functions to get the month as a string instead of a number<br />
*/<br />
<br />
Date.monthNames = [<br />
'January',<br />
'February',<br />
'March',<br />
'April',<br />
'May',<br />
'June',<br />
'July',<br />
'August',<br />
'September',<br />
'October',<br />
'November',<br />
'December'<br />
];<br />
<br />
Date.monthNamesAbbrev = [<br />
'Jan',<br />
'Feb',<br />
'Mar',<br />
'Apr',<br />
'May',<br />
'Jun',<br />
'Jul',<br />
'Aug',<br />
'Sep',<br />
'Oct',<br />
'Nov',<br />
'Dec'<br />
];<br />
<br />
Date.prototype.getMonthName = function() {<br />
return Date.monthNames[ this.getMonth() ];<br />
};<br />
<br />
Date.prototype.getMonthNameAbbrev = function() {<br />
return Date.monthNamesAbbrev[ this.getMonth() ];<br />
};<br />
<br />
Date.prototype.getUTCMonthName = function() {<br />
return Date.monthNames[ this.getUTCMonth() ];<br />
};<br />
<br />
Date.prototype.getUTCMonthNameAbbrev = function() {<br />
return Date.monthNamesAbbrev[ this.getUTCMonth() ];<br />
};<br />
<br />
/**<br />
* **************** Wikipedia ****************<br />
* Accessor functions for wikiediting and api-access<br />
*/<br />
<br />
var Wikipedia = {};<br />
<br />
Wikipedia.namespaces = {<br />
'-2': 'Media',<br />
'-1': 'Special',<br />
'0': '',<br />
'1': 'Talk',<br />
'2': 'User',<br />
'3': 'User talk',<br />
'4': 'Project',<br />
'5': 'Project talk',<br />
'6': 'File',<br />
'7': 'File talk',<br />
'8': 'MediaWiki',<br />
'9': 'MediaWiki talk',<br />
'10': 'Template',<br />
'11': 'Template talk',<br />
'12': 'Help',<br />
'13': 'Help talk',<br />
'14': 'Category',<br />
'15': 'Category talk',<br />
'100': 'Portal',<br />
'101': 'Portal talk',<br />
'108': 'Book',<br />
'109': 'Book talk'<br />
};<br />
<br />
Wikipedia.namespacesFriendly = {<br />
'0': '(Article)',<br />
'1': 'Talk',<br />
'2': 'User',<br />
'3': 'User talk',<br />
'4': 'Wikipedia',<br />
'5': 'Wikipedia talk',<br />
'6': 'File',<br />
'7': 'File talk',<br />
'8': 'MediaWiki',<br />
'9': 'MediaWiki talk',<br />
'10': 'Template',<br />
'11': 'Template talk',<br />
'12': 'Help',<br />
'13': 'Help talk',<br />
'14': 'Category',<br />
'15': 'Category talk',<br />
'100': 'Portal',<br />
'101': 'Portal talk',<br />
'108': 'Book',<br />
'109': 'Book talk'<br />
};<br />
<br />
// Analyzes the HTML of the current page (i.e. no AJAX requests) to determine if it<br />
// is a redirect or soft redirect<br />
Wikipedia.isPageRedirect = function wikipediaIsPageRedirect() {<br />
return !!($("span.redirectText").length > 0 || document.getElementById("softredirect"));<br />
};<br />
<br />
// we dump all XHR here so they won't loose props<br />
// REMOVEME after Wikipedia.wiki is gone<br />
Wikipedia.dump = [];<br />
<br />
<br />
/**<br />
* **************** Wikipedia.actionCompleted ****************<br />
*<br />
* Use of Wikipedia.actionCompleted():<br />
* Every call to Wikipedia.api.post() results in the dispatch of<br />
* an asynchronous callback. Each callback can in turn<br />
* make an additional call to Wikipedia.api.post() to continue a <br />
* processing sequence. At the conclusion of the final callback<br />
* of a processing sequence, it is not possible to simply return to the<br />
* original caller because there is no call stack leading back to<br />
* the original context. Instead, Wikipedia.actionCompleted.event() is<br />
* called to display the result to the user and to perform an optional<br />
* page redirect.<br />
*<br />
* The determination of when to call Wikipedia.actionCompleted.event()<br />
* is managed through the globals Wikipedia.numberOfActionsLeft and<br />
* Wikipedia.nbrOfCheckpointsLeft. Wikipedia.numberOfActionsLeft is<br />
* incremented at the start of every Wikipedia.api call and decremented <br />
* after the completion of a callback function. If a callback function<br />
* does not create a new Wikipedia.api object before exiting, it is the<br />
* final step in the processing chain and Wikipedia.actionCompleted.event()<br />
* will then be called.<br />
*<br />
* Optionally, callers may use Wikipedia.addCheckpoint() to indicate that<br />
* processing is not complete upon the conclusion of the final callback function.<br />
* This is used for batch operations. The end of a batch is signaled by calling<br />
* Wikipedia.removeCheckpoint(). <br />
*/<br />
<br />
Wikipedia.numberOfActionsLeft = 0;<br />
Wikipedia.nbrOfCheckpointsLeft = 0;<br />
<br />
Wikipedia.actionCompleted = function( self ) {<br />
if( --Wikipedia.numberOfActionsLeft <= 0 && Wikipedia.nbrOfCheckpointsLeft <= 0 ) {<br />
Wikipedia.actionCompleted.event( self );<br />
}<br />
};<br />
<br />
// Change per action wanted<br />
Wikipedia.actionCompleted.event = function() {<br />
new Status( Wikipedia.actionCompleted.notice, Wikipedia.actionCompleted.postfix, 'info' );<br />
if( Wikipedia.actionCompleted.redirect ) {<br />
// if it isn't a URL, which is likely, make it one. TODO: This breaks on the articles 'http://', 'ftp://', and similar ones. Are we ever using URL redirects?<br />
if( !( (/^\w+\:\/\//).test( Wikipedia.actionCompleted.redirect ) ) ) {<br />
Wikipedia.actionCompleted.redirect = mw.util.wikiGetlink( Wikipedia.actionCompleted.redirect );<br />
if( Wikipedia.actionCompleted.followRedirect === false ) {<br />
Wikipedia.actionCompleted.redirect += "?redirect=no";<br />
}<br />
}<br />
window.setTimeout( function() { window.location = Wikipedia.actionCompleted.redirect; }, Wikipedia.actionCompleted.timeOut );<br />
}<br />
};<br />
var wpActionCompletedTimeOut = typeof(wpActionCompletedTimeOut) === 'undefined' ? 5000 : wpActionCompletedTimeOut;<br />
var wpMaxLag = typeof(wpMaxLag) === 'undefined' ? 10 : wpMaxLag; // Maximum lag allowed, 5-10 is a good value, the higher value, the more agressive.<br />
<br />
// editCount - REMOVEME when Wikipedia.wiki is gone<br />
Wikipedia.editCount = 10;<br />
<br />
Wikipedia.actionCompleted.timeOut = wpActionCompletedTimeOut;<br />
Wikipedia.actionCompleted.redirect = null;<br />
Wikipedia.actionCompleted.notice = 'Action';<br />
Wikipedia.actionCompleted.postfix = 'completed';<br />
<br />
Wikipedia.addCheckpoint = function() {<br />
++Wikipedia.nbrOfCheckpointsLeft;<br />
};<br />
<br />
Wikipedia.removeCheckpoint = function() {<br />
if( --Wikipedia.nbrOfCheckpointsLeft <= 0 && Wikipedia.numberOfActionsLeft <= 0 ) {<br />
Wikipedia.actionCompleted.event();<br />
}<br />
};<br />
<br />
/**<br />
* **************** Wikipedia.api ****************<br />
*/<br />
<br />
/*<br />
currentAction: text, the current action (required)<br />
query: Object, the query (required)<br />
onSuccess: function, the function to call when page gotten<br />
onError: function, the function to call if an error occurs<br />
*/<br />
Wikipedia.api = function( currentAction, query, onSuccess, statusElement, onError ) {<br />
this.currentAction = currentAction;<br />
this.query = query;<br />
this.query.format = 'xml';<br />
this.onSuccess = onSuccess;<br />
this.onError = onError;<br />
if( statusElement ) {<br />
this.statelem = statusElement;<br />
this.statelem.status( currentAction );<br />
} else {<br />
this.statelem = new Status( currentAction );<br />
}<br />
};<br />
<br />
Wikipedia.api.prototype = {<br />
currentAction: '',<br />
onSuccess: null,<br />
onError: null,<br />
parent: window, // use global context if there is no parent object<br />
query: null,<br />
responseXML: null,<br />
setParent: function(parent) { this.parent = parent; }, // keep track of parent object for callbacks<br />
statelem: null, // this non-standard name kept for backwards compatibility<br />
statusText: null, // result received from the API, normally "success" or "error"<br />
errorCode: null, // short text error code, if any, as documented in the MediaWiki API<br />
errorText: null, // full error description, if any<br />
<br />
// post(): carries out the request<br />
// do not specify a parameter unless you really really want to give jQuery some extra parameters<br />
post: function( callerAjaxParameters ) {<br />
<br />
++Wikipedia.numberOfActionsLeft;<br />
<br />
var ajaxparams = $.extend( {}, {<br />
context: this,<br />
type: 'POST',<br />
url: mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/api.php',<br />
data: QueryString.create(this.query),<br />
datatype: 'xml',<br />
<br />
success: function(xml, statusText, jqXHR) {<br />
this.statusText = statusText;<br />
this.responseXML = xml;<br />
this.errorCode = $(xml).find('error').attr('code');<br />
this.errorText = $(xml).find('error').attr('info');<br />
<br />
if (typeof(this.errorCode) === "string") {<br />
<br />
// the API didn't like what we told it, e.g., bad edit token or an error creating a page<br />
this.returnError();<br />
return;<br />
}<br />
<br />
// invoke success callback if one was supplied<br />
if (this.onSuccess) {<br />
<br />
// set the callback context to this.parent for new code and supply the API object<br />
// as the first argument to the callback (for legacy code)<br />
this.onSuccess.call( this.parent, this );<br />
} else {<br />
this.statelem.info("done");<br />
}<br />
<br />
Wikipedia.actionCompleted();<br />
},<br />
<br />
// only network and server errors reach here – complaints from the API itself are caught in success()<br />
error: function(jqXHR, statusText, errorThrown) {<br />
this.statusText = statusText;<br />
this.errorThrown = errorThrown; // frequently undefined<br />
this.errorText = statusText + ' "' + jqXHR.statusText + '" occurred while contacting the API.';<br />
this.returnError();<br />
}<br />
<br />
}, callerAjaxParameters );<br />
<br />
return $.ajax( ajaxparams ); // the return value should be ignored, unless using callerAjaxParameters with |async: false|<br />
},<br />
<br />
returnError: function() {<br />
<br />
// invoke failure callback if one was supplied<br />
if (this.onError) {<br />
<br />
// set the callback context to this.parent for new code and supply the API object<br />
// as the first argument to the callback for legacy code<br />
this.onError.call( this.parent, this );<br />
} else {<br />
this.statelem.error( this.errorText );<br />
}<br />
// don't complete the action so that the error remains displayed<br />
},<br />
<br />
getStatusElement: function() {<br />
return this.statelem;<br />
},<br />
<br />
getErrorCode: function() {<br />
return this.errorCode;<br />
},<br />
<br />
getErrorText: function() {<br />
return this.errorText;<br />
},<br />
<br />
getXML: function() {<br />
return this.responseXML;<br />
}<br />
};<br />
<br />
/**<br />
* **************** Wikipedia.page ****************<br />
* Uses the MediaWiki API to load a page and optionally edit it, move it, etc.<br />
*<br />
* Callers are not permitted to directly access the properties of this class!<br />
* All property access is through the appropriate get___() or set___() method.<br />
*<br />
* Callers should set Wikipedia.actionCompleted.notice and Wikipedia.actionCompleted.redirect<br />
* before the first call to Wikipedia.page.load().<br />
*<br />
* Each of the callback functions takes one parameter, which is a<br />
* reference to the Wikipedia.page object that registered the callback.<br />
* Callback functions may invoke any Wikipedia.page prototype method using this reference.<br />
*<br />
* Constructor: Wikipedia.page(pageName, currentAction)<br />
* pageName - the name of the page, prefixed by the namespace (if any)<br />
* (for the current page, use mw.config.get('wgPageName'))<br />
* currentAction - a string describing the action about to be undertaken (optional)<br />
*<br />
* load(onSuccess, onFailure): Loads the text for the page<br />
* onSuccess - callback function which is called when the load has succeeded<br />
* onFailure - callback function which is called when the load fails (optional)<br />
* XXX onFailure for load() is not yet implemented – do we need it? -- UncleDouggie<br />
* probably not -- TTO<br />
*<br />
* save(onSuccess, onFailure): Saves the text for the page. Must be preceded by calling load().<br />
* onSuccess - callback function which is called when the save has succeeded (optional)<br />
* onFailure - callback function which is called when the save fails (optional)<br />
* Warning: Calling save() can result in additional calls to the previous load() callbacks to<br />
* recover from edit conflicts! <br />
* In this case, callers must make the same edit to the new pageText and reinvoke save(). <br />
* This behavior can be disabled with setMaxConflictRetries(0).<br />
*<br />
* append(onSuccess, onFailure): Adds the text provided via setAppendText() to the end of the page.<br />
* Does not require calling load() first.<br />
* onSuccess - callback function which is called when the method has succeeded (optional)<br />
* onFailure - callback function which is called when the method fails (optional)<br />
*<br />
* prepend(onSuccess, onFailure): Adds the text provided via setPrependText() to the start of the page.<br />
* Does not require calling load() first.<br />
* onSuccess - callback function which is called when the method has succeeded (optional)<br />
* onFailure - callback function which is called when the method fails (optional)<br />
*<br />
* getPageName(): returns a string containing the name of the loaded page, including the namespace<br />
*<br />
* getPageText(): returns a string containing the text of the page after a successful load()<br />
*<br />
* setPageText(pageText) <br />
* pageText - string containing the updated page text that will be saved when save() is called<br />
*<br />
* setAppendText(appendText) <br />
* appendText - string containing the text that will be appended to the page when append() is called<br />
*<br />
* setPrependText(prependText) <br />
* prependText - string containing the text that will be prepended to the page when prepend() is called<br />
*<br />
* setEditSummary(summary)<br />
* summary - string containing the text of the edit summary that will be used when save() is called<br />
*<br />
* setMinorEdit(minorEdit) <br />
* minorEdit is a boolean value:<br />
* true - When save is called, the resulting edit will be marked as "minor".<br />
* false - When save is called, the resulting edit will not be marked as "minor". (default)<br />
*<br />
* setPageSection(pageSection)<br />
* pageSection - integer specifying the section number to load or save. The default is |null|, which means<br />
* that the entire page will be retrieved.<br />
*<br />
* setMaxConflictRetries(maxRetries)<br />
* maxRetries - number of retries for save errors involving an edit conflict or loss of edit token<br />
* default: 2<br />
*<br />
* setMaxRetries(maxRetries)<br />
* maxRetries - number of retries for save errors not involving an edit conflict or loss of edit token<br />
* default: 2<br />
*<br />
* setCallbackParameters(callbackParameters)<br />
* callbackParameters - an object for use in a callback function<br />
*<br />
* getCallbackParameters(): returns the object previous set by setCallbackParameters()<br />
*<br />
* Callback notes: callbackParameters is for use by the caller only. The parameters<br />
* allow a caller to pass the proper context into its callback function.<br />
* Callers must ensure that any changes to the callbackParameters object<br />
* within a load() callback still permit a proper re-entry into the<br />
* load() callback if an edit conflict is detected upon calling save().<br />
*<br />
* getStatusElement(): returns the Status element created by the constructor<br />
*<br />
* setFollowRedirect(followRedirect)<br />
* followRedirect is a boolean value:<br />
* true - a maximum of one redirect will be followed.<br />
* In the event of a redirect, a message is displayed to the user and <br />
* the redirect target can be retrieved with getPageName().<br />
* false - the requested pageName will be used without regard to any redirect. (default)<br />
*<br />
* setWatchlist(watchlistOption)<br />
* watchlistOption is a boolean value:<br />
* true - page will be added to the user's watchlist when save() is called<br />
* false - watchlist status of the page will not be changed (default)<br />
*<br />
* setWatchlistFromPreferences(watchlistOption)<br />
* watchlistOption is a boolean value:<br />
* true - page watchlist status will be set based on the user's <br />
* preference settings when save() is called<br />
* false - watchlist status of the page will not be changed (default)<br />
*<br />
* Watchlist notes:<br />
* 1. The MediaWiki API value of 'unwatch', which explicitly removes the page from the<br />
* user's watchlist, is not used.<br />
* 2. If both setWatchlist() and setWatchlistFromPreferences() are called,<br />
* the last call takes priority.<br />
* 3. Twinkle modules should use the appropriate preference to set the watchlist options.<br />
* 4. Most Twinkle modules use setWatchlist().<br />
* setWatchlistFromPreferences() is only needed for the few Twinkle watchlist preferences<br />
* that accept a string value of 'default'.<br />
*<br />
* setCreateOption(createOption)<br />
* createOption is a string value:<br />
* 'recreate' - create the page if it does not exist, or edit it if it exists<br />
* 'createonly' - create the page if it does not exist, but return an error if it<br />
* already exists<br />
* 'nocreate' - don't create the page, only edit it if it already exists<br />
* null - create the page if it does not exist, unless it was deleted in the moment<br />
* between retrieving the edit token and saving the edit (default)<br />
*<br />
* exists(): returns true if the page existed on the wiki when it was last loaded<br />
*<br />
* lookupCreator(onSuccess): Retrieves the username of the user who created the page<br />
* onSuccess - callback function which is called when the username is found<br />
* within the callback, the username can be retrieved using the getCreator() function<br />
* <br />
* getCreator(): returns the user who created the page following lookupCreator()<br />
*<br />
* patrol(): marks the page as patrolled (only when "rcid" is present in the query string)<br />
*<br />
* move(onSuccess, onFailure): Moves a page to another title<br />
*<br />
* deletePage(onSuccess, onFailure): Deletes a page (for admins only)<br />
*<br />
*/<br />
<br />
/**<br />
* Call sequence for common operations (optional final user callbacks not shown):<br />
*<br />
* Edit current contents of a page (no edit conflict):<br />
* .load(userTextEditCallback) -> ctx.loadApi.post() -> ctx.loadApi.post.success() -> <br />
* ctx.fnLoadSuccess() -> userTextEditCallback() -> .save() -> <br />
* ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveSuccess()<br />
*<br />
* Edit current contents of a page (with edit conflict):<br />
* .load(userTextEditCallback) -> ctx.loadApi.post() -> ctx.loadApi.post.success() -> <br />
* ctx.fnLoadSuccess() -> userTextEditCallback() -> .save() -> <br />
* ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveError() -><br />
* ctx.loadApi.post() -> ctx.loadApi.post.success() -> <br />
* ctx.fnLoadSuccess() -> userTextEditCallback() -> .save() -> <br />
* ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveSuccess()<br />
*<br />
* Append to a page (similar for prepend):<br />
* .append() -> ctx.loadApi.post() -> ctx.loadApi.post.success() -> <br />
* ctx.fnLoadSuccess() -> ctx.fnAutoSave() -> .save() -> <br />
* ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveSuccess()<br />
*<br />
* Notes: <br />
* 1. All functions following Wikipedia.api.post() are invoked asynchronously <br />
* from the jQuery AJAX library.<br />
* 2. The sequence for append/prepend could be slightly shortened, but it would require<br />
* significant duplication of code for little benefit.<br />
*/<br />
<br />
Wikipedia.page = function(pageName, currentAction) {<br />
<br />
if (!currentAction) {<br />
currentAction = 'Opening page "' + pageName + '"';<br />
}<br />
<br />
/**<br />
* Private context variables<br />
*<br />
* This context is not visible to the outside, thus all the data here<br />
* must be accessed via getter and setter functions.<br />
*/<br />
var ctx = {<br />
// backing fields for public properties<br />
pageName: pageName,<br />
pageText: null,<br />
editMode: 'all', // save() replaces entire contents of the page by default<br />
appendText: null, // can't reuse pageText for this because pageText is needed to follow a redirect<br />
prependText: null, // can't reuse pageText for this because pageText is needed to follow a redirect<br />
editSummary: null,<br />
createOption: null,<br />
minorEdit: false,<br />
pageSection: null,<br />
maxConflictRetries: 2,<br />
maxRetries: 2,<br />
callbackParameters: null,<br />
statusElement: new Status(currentAction),<br />
followRedirect: false,<br />
watchlistOption: 'nochange',<br />
pageExists: false,<br />
creator: null,<br />
revertOldID: null,<br />
moveDestination: null,<br />
moveTalkPage: false,<br />
moveSubpages: false,<br />
moveSuppressRedirect: false,<br />
protectEdit: null,<br />
protectMove: null,<br />
protectCreate: null,<br />
protectCascade: false,<br />
// internal status<br />
pageLoaded: false,<br />
editToken: null,<br />
loadTime: null,<br />
lastEditTime: null,<br />
revertCurID: null,<br />
revertUser: null,<br />
fullyProtected: false,<br />
conflictRetries: 0,<br />
retries: 0,<br />
// callbacks<br />
onLoadSuccess: null,<br />
onLoadFailure: null,<br />
onSaveSuccess: null,<br />
onSaveFailure: null,<br />
onLookupCreatorSuccess: null,<br />
onMoveSuccess: null,<br />
onMoveFailure: null,<br />
onDeleteSuccess: null,<br />
onDeleteFailure: null,<br />
onProtectSuccess: null,<br />
onProtectFailure: null,<br />
// internal objects<br />
loadQuery: null,<br />
loadApi: null,<br />
saveApi: null,<br />
lookupCreatorApi: null,<br />
moveApi: null,<br />
moveProcessApi: null,<br />
deleteApi: null,<br />
deleteProcessApi: null,<br />
protectApi: null,<br />
protectProcessApi: null<br />
};<br />
<br />
/**<br />
* Public interface accessors<br />
*/<br />
this.getPageName = function() {<br />
return ctx.pageName;<br />
};<br />
<br />
this.getPageText = function() {<br />
return ctx.pageText;<br />
};<br />
<br />
this.setPageText = function(pageText) {<br />
ctx.editMode = 'all';<br />
ctx.pageText = pageText;<br />
};<br />
<br />
this.setAppendText = function(appendText) {<br />
ctx.editMode = 'append';<br />
ctx.appendText = appendText;<br />
};<br />
<br />
this.setPrependText = function(prependText) {<br />
ctx.editMode = 'prepend';<br />
ctx.prependText = prependText;<br />
};<br />
<br />
this.setEditSummary = function(summary) {<br />
ctx.editSummary = summary;<br />
};<br />
<br />
this.setCreateOption = function(createOption) {<br />
ctx.createOption = createOption;<br />
};<br />
<br />
this.setMinorEdit = function(minorEdit) {<br />
ctx.minorEdit = minorEdit;<br />
};<br />
<br />
this.setPageSection = function(pageSection) {<br />
ctx.pageSection = pageSection;<br />
};<br />
<br />
this.setMaxConflictRetries = function(maxRetries) {<br />
ctx.maxConflictRetries = maxRetries;<br />
};<br />
<br />
this.setMaxRetries = function(maxRetries) {<br />
ctx.maxRetries = maxRetries;<br />
};<br />
<br />
this.setCallbackParameters = function(callbackParameters) {<br />
ctx.callbackParameters = callbackParameters;<br />
};<br />
<br />
this.getCallbackParameters = function() {<br />
return ctx.callbackParameters;<br />
};<br />
<br />
this.getCreator = function() {<br />
return ctx.creator;<br />
};<br />
<br />
this.setOldID = function(oldID) {<br />
ctx.revertOldID = oldID;<br />
};<br />
<br />
this.getRevisionUser = function() {<br />
return ctx.revertUser;<br />
};<br />
<br />
this.setMoveDestination = function(destination) {<br />
ctx.moveDestination = destination;<br />
};<br />
<br />
this.setMoveTalkPage = function(flag) {<br />
ctx.moveTalkPage = !!flag;<br />
};<br />
<br />
this.setMoveSubpages = function(flag) {<br />
ctx.moveSubpages = !!flag;<br />
};<br />
<br />
this.setMoveSuppressRedirect = function(flag) {<br />
ctx.moveSuppressRedirect = !!flag;<br />
};<br />
<br />
this.setEditProtection = function(level, expiry) {<br />
ctx.protectEdit = { level: level, expiry: expiry };<br />
};<br />
<br />
this.setMoveProtection = function(level, expiry) {<br />
ctx.protectMove = { level: level, expiry: expiry };<br />
};<br />
<br />
this.setCreateProtection = function(level, expiry) {<br />
ctx.protectCreate = { level: level, expiry: expiry };<br />
};<br />
<br />
this.setCascadingProtection = function(flag) {<br />
ctx.protectCascade = !!flag;<br />
};<br />
<br />
this.getStatusElement = function() {<br />
return ctx.statusElement;<br />
};<br />
<br />
this.setFollowRedirect = function(followRedirect) {<br />
if (ctx.pageLoaded) {<br />
ctx.statusElement.error("Internal error: cannot change redirect setting after the page has been loaded!");<br />
return;<br />
}<br />
ctx.followRedirect = followRedirect;<br />
};<br />
<br />
this.setWatchlist = function(flag) {<br />
if (flag) {<br />
ctx.watchlistOption = 'watch';<br />
} else {<br />
ctx.watchlistOption = 'nochange';<br />
}<br />
};<br />
<br />
this.setWatchlistFromPreferences = function(flag) {<br />
if (flag) {<br />
ctx.watchlistOption = 'preferences';<br />
} else {<br />
ctx.watchlistOption = 'nochange';<br />
}<br />
};<br />
<br />
this.exists = function() {<br />
return ctx.pageExists;<br />
};<br />
<br />
this.load = function(onSuccess, onFailure) {<br />
ctx.onLoadSuccess = onSuccess;<br />
ctx.onLoadFailure = onFailure;<br />
<br />
// Need to be able to do something after the page loads<br />
if (!onSuccess) {<br />
ctx.statusElement.error("Internal error: no onSuccess callback provided to load()!");<br />
return;<br />
}<br />
<br />
ctx.loadQuery = {<br />
action: 'query',<br />
prop: 'info|revisions',<br />
intoken: 'edit', // fetch an edit token<br />
titles: ctx.pageName<br />
// don't need rvlimit=1 because we don't need rvstartid here and only one actual rev is returned by default<br />
};<br />
<br />
if (ctx.editMode === 'all') {<br />
ctx.loadQuery.rvprop = 'content'; // get the page content at the same time, if needed<br />
} else if (ctx.editMode === 'revert') {<br />
ctx.loadQuery.rvlimit = 1;<br />
ctx.loadQuery.rvstartid = ctx.revertOldID;<br />
}<br />
<br />
if (ctx.followRedirect) {<br />
ctx.loadQuery.redirects = ''; // follow all redirects<br />
}<br />
if (typeof(ctx.pageSection) === 'number') {<br />
ctx.loadQuery.rvsection = ctx.pageSection;<br />
}<br />
if (userIsInGroup('sysop')) {<br />
ctx.loadQuery.inprop = 'protection';<br />
}<br />
<br />
ctx.loadApi = new Wikipedia.api("Retrieving page...", ctx.loadQuery, fnLoadSuccess, ctx.statusElement);<br />
ctx.loadApi.setParent(this);<br />
ctx.loadApi.post();<br />
};<br />
<br />
// Save updated .pageText to Wikipedia<br />
// Only valid after successful .load()<br />
this.save = function(onSuccess, onFailure) {<br />
if (!ctx.pageLoaded) {<br />
ctx.statusElement.error("Internal error: attempt to save a page that has not been loaded!");<br />
return;<br />
}<br />
if (!ctx.editSummary) {<br />
ctx.statusElement.error("Internal error: edit summary not set before save!");<br />
return;<br />
}<br />
<br />
if (ctx.fullyProtected && !confirm('You are about to make an edit to the fully protected page "' + ctx.pageName +<br />
(ctx.fullyProtected === 'infinity' ? '" (protected indefinitely)' : ('" (protection expiring ' + ctx.fullyProtected + ')')) +<br />
'. \n\nClick OK to proceed with the edit, or Cancel to skip this edit.')) {<br />
ctx.statusElement.error("Edit to fully protected page was aborted.");<br />
return;<br />
}<br />
<br />
ctx.onSaveSuccess = onSuccess;<br />
ctx.onSaveFailure = onFailure;<br />
ctx.retries = 0;<br />
<br />
var query = {<br />
action: 'edit',<br />
title: ctx.pageName,<br />
summary: ctx.editSummary,<br />
token: ctx.editToken,<br />
watchlist: ctx.watchlistOption<br />
};<br />
<br />
if (typeof(ctx.pageSection) === 'number') {<br />
query.section = ctx.pageSection;<br />
}<br />
<br />
// Set minor edit attribute. If these parameters are present with any value, it is interpreted as true<br />
if (ctx.minorEdit) {<br />
query.minor = true;<br />
} else {<br />
query.notminor = true; // force Twinkle config to override user preference setting for "all edits are minor"<br />
}<br />
<br />
switch (ctx.editMode) {<br />
case 'append':<br />
query.appendtext = ctx.appendText; // use mode to append to current page contents<br />
break;<br />
case 'prepend':<br />
query.prependtext = ctx.prependText; // use mode to prepend to current page contents<br />
break;<br />
case 'revert':<br />
query.undo = ctx.revertCurID;<br />
query.undoafter = ctx.revertOldID;<br />
if (ctx.lastEditTime) {<br />
query.basetimestamp = ctx.lastEditTime; // check that page hasn't been edited since it was loaded<br />
}<br />
query.starttimestamp = ctx.loadTime; // check that page hasn't been deleted since it was loaded (don't recreate bad stuff)<br />
break;<br />
default:<br />
query.text = ctx.pageText; // replace entire contents of the page<br />
if (ctx.lastEditTime) {<br />
query.basetimestamp = ctx.lastEditTime; // check that page hasn't been edited since it was loaded<br />
}<br />
query.starttimestamp = ctx.loadTime; // check that page hasn't been deleted since it was loaded (don't recreate bad stuff)<br />
break;<br />
}<br />
<br />
if (['recreate', 'createonly', 'nocreate'].indexOf(ctx.createOption) !== -1) {<br />
query[ctx.createOption] = '';<br />
}<br />
<br />
ctx.saveApi = new Wikipedia.api( "Saving page...", query, fnSaveSuccess, ctx.statusElement, fnSaveError);<br />
ctx.saveApi.setParent(this);<br />
ctx.saveApi.post();<br />
};<br />
<br />
this.append = function(onSuccess, onFailure) {<br />
ctx.editMode = 'append';<br />
ctx.onSaveSuccess = onSuccess;<br />
ctx.onSaveFailure = onFailure;<br />
this.load(fnAutoSave, onFailure);<br />
};<br />
<br />
this.prepend = function(onSuccess, onFailure) {<br />
ctx.editMode = 'prepend';<br />
ctx.onSaveSuccess = onSuccess;<br />
ctx.onSaveFailure = onFailure;<br />
this.load(fnAutoSave, onFailure);<br />
};<br />
<br />
this.lookupCreator = function(onSuccess) {<br />
if (!onSuccess) {<br />
ctx.statusElement.error("Internal error: no onSuccess callback provided to lookupCreator()!");<br />
return;<br />
}<br />
ctx.onLookupCreatorSuccess = onSuccess;<br />
<br />
var query = {<br />
'action': 'query',<br />
'prop': 'revisions',<br />
'titles': ctx.pageName,<br />
'rvlimit': 1,<br />
'rvprop': 'user',<br />
'rvdir': 'newer'<br />
};<br />
<br />
if (ctx.followRedirect) {<br />
query.redirects = ''; // follow all redirects<br />
}<br />
<br />
ctx.lookupCreatorApi = new Wikipedia.api("Retrieving page creator information", query, fnLookupCreatorSuccess, ctx.statusElement);<br />
ctx.lookupCreatorApi.setParent(this);<br />
ctx.lookupCreatorApi.post();<br />
};<br />
<br />
this.patrol = function() {<br />
// look for rcid in querystring; if not, we won't have a patrol token, so no point trying<br />
if (!QueryString.exists("rcid")) {<br />
return;<br />
}<br />
var rcid = QueryString.get("rcid");<br />
<br />
// extract patrol token from "Mark page as patrolled" link on page<br />
var patrollinkmatch = /token=(.+)%2B%5C$/.exec($(".patrollink a").attr("href"));<br />
if (patrollinkmatch) {<br />
var patroltoken = patrollinkmatch[1] + "+\\";<br />
var patrolstat = new Status("Marking page as patrolled");<br />
<br />
var wikipedia_api = new Wikipedia.api("doing...", {<br />
title: ctx.pageName,<br />
action: 'markpatrolled',<br />
rcid: rcid,<br />
token: patroltoken<br />
}, null, patrolstat);<br />
wikipedia_api.post({<br />
type: 'GET',<br />
url: mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php',<br />
datatype: 'text' // we don't really care about the response<br />
});<br />
}<br />
};<br />
<br />
this.revert = function(onSuccess, onFailure) {<br />
if (!ctx.revertOldID) {<br />
ctx.statusElement.error("Internal error: revision ID to revert to was not set before revert!");<br />
return;<br />
}<br />
ctx.editMode = 'revert';<br />
ctx.onSaveSuccess = onSuccess;<br />
ctx.onSaveFailure = onFailure;<br />
this.load(fnAutoSave, onFailure);<br />
};<br />
<br />
this.move = function(onSuccess, onFailure) {<br />
if (!ctx.editSummary) {<br />
ctx.statusElement.error("Internal error: move reason not set before move (use setEditSummary function)!");<br />
return;<br />
}<br />
if (!ctx.moveDestination) {<br />
ctx.statusElement.error("Internal error: destination page name was not set before move!");<br />
return;<br />
}<br />
<br />
ctx.onMoveSuccess = onSuccess;<br />
ctx.onMoveFailure = onFailure;<br />
<br />
var query = {<br />
action: 'query',<br />
prop: 'info',<br />
intoken: 'move',<br />
titles: ctx.pageName<br />
};<br />
if (ctx.followRedirect) {<br />
query.redirects = ''; // follow all redirects<br />
}<br />
if (userIsInGroup('sysop')) {<br />
query.inprop = 'protection';<br />
}<br />
<br />
ctx.moveApi = new Wikipedia.api("retrieving move token...", query, fnProcessMove, ctx.statusElement);<br />
ctx.moveApi.setParent(this);<br />
ctx.moveApi.post();<br />
};<br />
<br />
// |delete| is a reserved word in some flavours of JS<br />
this.deletePage = function(onSuccess, onFailure) {<br />
// if a non-admin tries to do this, don't bother<br />
if (!userIsInGroup('sysop')) {<br />
ctx.statusElement.error("Cannot delete page: only admins can do that");<br />
return;<br />
}<br />
if (!ctx.editSummary) {<br />
ctx.statusElement.error("Internal error: delete reason not set before delete (use setEditSummary function)!");<br />
return;<br />
}<br />
<br />
ctx.onDeleteSuccess = onSuccess;<br />
ctx.onDeleteFailure = onFailure;<br />
<br />
var query = {<br />
action: 'query',<br />
prop: 'info',<br />
inprop: 'protection',<br />
intoken: 'delete',<br />
titles: ctx.pageName<br />
};<br />
if (ctx.followRedirect) {<br />
query.redirects = ''; // follow all redirects<br />
}<br />
<br />
ctx.deleteApi = new Wikipedia.api("retrieving delete token...", query, fnProcessDelete, ctx.statusElement);<br />
ctx.deleteApi.setParent(this);<br />
ctx.deleteApi.post();<br />
};<br />
<br />
this.protect = function(onSuccess, onFailure) {<br />
// if a non-admin tries to do this, don't bother<br />
if (!userIsInGroup('sysop')) {<br />
ctx.statusElement.error("Cannot protect page: only admins can do that");<br />
return;<br />
}<br />
if (!ctx.protectEdit && !ctx.protectMove && !ctx.protectCreate) {<br />
ctx.statusElement.error("Internal error: you must set edit and/or move and/or create protection before calling protect()!");<br />
return;<br />
}<br />
if (!ctx.editSummary) {<br />
ctx.statusElement.error("Internal error: protection reason not set before protect (use setEditSummary function)!");<br />
return;<br />
}<br />
<br />
ctx.onProtectSuccess = onSuccess;<br />
ctx.onProtectFailure = onFailure;<br />
<br />
var query = {<br />
action: 'query',<br />
prop: 'info',<br />
inprop: 'protection',<br />
intoken: 'protect',<br />
titles: ctx.pageName<br />
};<br />
if (ctx.followRedirect) {<br />
query.redirects = ''; // follow all redirects<br />
}<br />
<br />
ctx.protectApi = new Wikipedia.api("retrieving protect token...", query, fnProcessProtect, ctx.statusElement);<br />
ctx.protectApi.setParent(this);<br />
ctx.protectApi.post();<br />
};<br />
<br />
/**<br />
* Private member functions<br />
*<br />
* These are not exposed outside<br />
*/<br />
<br />
// callback from loadSuccess() for append() and prepend() threads<br />
var fnAutoSave = function(pageobj) {<br />
pageobj.save(ctx.onSaveSuccess, ctx.onSaveFailure);<br />
};<br />
<br />
// callback from loadApi.post()<br />
var fnLoadSuccess = function() {<br />
var xml = ctx.loadApi.getXML();<br />
<br />
if ( !fnCheckPageName(xml) ) {<br />
return; // abort<br />
}<br />
<br />
ctx.pageExists = ($(xml).find('page').attr('missing') !== "");<br />
if (ctx.pageExists) {<br />
ctx.pageText = $(xml).find('rev').text();<br />
} else {<br />
ctx.pageText = ''; // allow for concatenation, etc.<br />
}<br />
<br />
// extract protection info, to alert admins when they are about to edit a protected page<br />
if (userIsInGroup('sysop')) {<br />
var editprot = $(xml).find('pr[type="edit"]');<br />
if (editprot.length > 0 && editprot.attr('level') === 'sysop') {<br />
ctx.fullyProtected = editprot.attr('expiry');<br />
} else {<br />
ctx.fullyProtected = false;<br />
}<br />
}<br />
<br />
ctx.editToken = $(xml).find('page').attr('edittoken');<br />
if (!ctx.editToken)<br />
{<br />
ctx.statusElement.error("Failed to retrieve edit token.");<br />
return;<br />
}<br />
ctx.loadTime = $(xml).find('page').attr('starttimestamp');<br />
if (!ctx.loadTime)<br />
{<br />
ctx.statusElement.error("Failed to retrieve start timestamp.");<br />
return;<br />
}<br />
ctx.lastEditTime = $(xml).find('page').attr('touched');<br />
<br />
if (ctx.editMode === 'revert') {<br />
ctx.revertCurID = $(xml).find('rev').attr('revid');<br />
if (!ctx.revertCurID) {<br />
ctx.statusElement.error("Failed to retrieve current revision ID.");<br />
return;<br />
}<br />
ctx.revertUser = $(xml).find('rev').attr('user');<br />
if (!ctx.revertUser) {<br />
if ($(xml).find('rev').attr('userhidden') === "") { // username was RevDel'd or oversighted<br />
ctx.revertUser = "<username hidden>";<br />
} else {<br />
ctx.statusElement.error("Failed to retrieve user who made the revision.");<br />
return;<br />
}<br />
}<br />
// set revert edit summary<br />
ctx.editSummary = "[[Help:Revert|Reverted]] to revision " + ctx.revertOldID + " by " + ctx.revertUser + ": " + ctx.editSummary;<br />
}<br />
<br />
ctx.pageLoaded = true;<br />
<br />
// alert("Generate edit conflict now"); // for testing edit conflict recovery logic<br />
ctx.onLoadSuccess(this); // invoke callback<br />
};<br />
<br />
// helper function to parse the page name returned from the API<br />
var fnCheckPageName = function(xml) {<br />
<br />
// check for invalid titles<br />
if ( $(xml).find('page').attr('invalid') ) {<br />
ctx.statusElement.error("Attempt to edit a page with invalid title: " + ctx.pageName);<br />
return false; // abort<br />
}<br />
<br />
// retrieve actual title of the page after normalization and redirects<br />
if ( $(xml).find('page').attr('title') ) {<br />
var resolvedName = $(xml).find('page').attr('title');<br />
<br />
// only notify user for redirects, not normalization<br />
if ( $(xml).find('redirects').length > 0 ) {<br />
Status.info("Info", "Redirected from " + ctx.pageName + " to " + resolvedName );<br />
}<br />
ctx.pageName = resolvedName; // always update in case of normalization<br />
}<br />
else {<br />
// could be a circular redirect or other problem<br />
ctx.statusElement.error("Could not resolve redirects for: " + ctx.pageName);<br />
<br />
// force error to stay on the screen<br />
++Wikipedia.numberOfActionsLeft;<br />
return false; // abort<br />
}<br />
return true; // all OK<br />
};<br />
<br />
// callback from saveApi.post()<br />
var fnSaveSuccess = function() {<br />
ctx.editMode = 'all'; // cancel append/prepend/revert modes<br />
var xml = ctx.saveApi.getXML();<br />
<br />
// see if the API thinks we were successful<br />
if ($(xml).find('edit').attr('result') === "Success") {<br />
<br />
// real success<br />
if (ctx.onSaveSuccess) {<br />
ctx.onSaveSuccess(this); // invoke callback<br />
} else {<br />
// default on success action - display link for edited page<br />
var link = document.createElement('a');<br />
link.setAttribute('href', mw.util.wikiGetlink(ctx.pageName) );<br />
link.appendChild(document.createTextNode(ctx.pageName));<br />
ctx.statusElement.info(['completed (', link, ')']);<br />
}<br />
return;<br />
}<br />
<br />
// errors here are only generated by extensions which hook APIEditBeforeSave within MediaWiki<br />
// Wikimedia wikis should only return spam blacklist errors and captchas<br />
var blacklist = $(xml).find('edit').attr('spamblacklist');<br />
<br />
if (blacklist) {<br />
var code = document.createElement('code');<br />
code.style.fontFamily = "monospace";<br />
code.appendChild(document.createTextNode(blacklist));<br />
ctx.statusElement.error(['Could not save the page because the URL ', code, ' is on the spam blacklist.']);<br />
}<br />
else if ( $(xml).find('captcha').length > 0 ) {<br />
ctx.statusElement.error("Could not save the page because the wiki server wanted you to fill out a CAPTCHA.");<br />
}<br />
else {<br />
ctx.statusElement.error("Unknown error received from API while saving page");<br />
}<br />
<br />
// force error to stay on the screen<br />
++Wikipedia.numberOfActionsLeft;<br />
};<br />
<br />
// callback from saveApi.post()<br />
var fnSaveError = function() {<br />
<br />
var errorCode = ctx.saveApi.getErrorCode();<br />
<br />
// check for edit conflict<br />
if ( errorCode === "editconflict" && ctx.conflictRetries++ < ctx.maxConflictRetries ) {<br />
<br />
// edit conflicts can occur when the page needs to be purged from the server cache<br />
var purgeQuery = {<br />
action: 'purge',<br />
titles: ctx.pageName // redirects are already resolved<br />
};<br />
<br />
var purgeApi = new Wikipedia.api("Edit conflict detected, purging server cache", purgeQuery, null, ctx.statusElement);<br />
var result = purgeApi.post( { async: false } ); // just wait for it, result is for debugging<br />
<br />
--Wikipedia.numberOfActionsLeft; // allow for normal completion if retry succeeds<br />
<br />
ctx.statusElement.info("Edit conflict detected, reapplying edit");<br />
ctx.loadApi.post(); // reload the page and reapply the edit<br />
<br />
// check for loss of edit token<br />
// it's impractical to request a new token here, so invoke edit conflict logic when this happens<br />
} else if ( errorCode === "notoken" && ctx.conflictRetries++ < ctx.maxConflictRetries ) {<br />
<br />
ctx.statusElement.info("Edit token is invalid, retrying");<br />
--Wikipedia.numberOfActionsLeft; // allow for normal completion if retry succeeds<br />
ctx.loadApi.post(); // reload<br />
<br />
// check for network or server error<br />
} else if ( errorCode === "undefined" && ctx.retries++ < ctx.maxRetries ) {<br />
<br />
// the error might be transient, so try again<br />
ctx.statusElement.info("Save failed, retrying");<br />
--Wikipedia.numberOfActionsLeft; // allow for normal completion if retry succeeds<br />
ctx.saveApi.post(); // give it another go!<br />
<br />
// hard error, give up<br />
} else {<br />
<br />
// non-admin attempting to edit a protected page - this gives a friendlier message than the default<br />
if ( errorCode === "protectedpage" ) {<br />
ctx.statusElement.error( "Failed to save edit: Page is fully protected" );<br />
} else {<br />
ctx.statusElement.error( "Failed to save edit: " + ctx.saveApi.getErrorText() );<br />
}<br />
ctx.editMode = 'all'; // cancel append/prepend/revert modes<br />
if (ctx.onSaveFailure) {<br />
ctx.onSaveFailure(this); // invoke callback<br />
}<br />
}<br />
};<br />
<br />
var fnLookupCreatorSuccess = function() {<br />
var xml = ctx.lookupCreatorApi.getXML();<br />
<br />
if ( !fnCheckPageName(xml) ) {<br />
return; // abort<br />
}<br />
<br />
ctx.creator = $(xml).find('rev').attr('user');<br />
if (!ctx.creator) {<br />
ctx.statusElement.error("Could not find name of page creator");<br />
return;<br />
}<br />
ctx.onLookupCreatorSuccess(this);<br />
};<br />
<br />
var fnProcessMove = function() {<br />
var xml = ctx.moveApi.getXML();<br />
<br />
if ($(xml).find('page').attr('missing') === "") {<br />
ctx.statusElement.error("Cannot move the page, because it no longer exists");<br />
return;<br />
}<br />
<br />
// extract protection info<br />
if (userIsInGroup('sysop')) {<br />
var editprot = $(xml).find('pr[type="edit"]');<br />
if (editprot.length > 0 && editprot.attr('level') === 'sysop' && !confirm('You are about to move the fully protected page "' + ctx.pageName + <br />
(editprot.attr('expiry') === 'infinity' ? '" (protected indefinitely)' : ('" (protection expiring ' + editprot.attr('expiry') + ')')) +<br />
'. \n\nClick OK to proceed with the move, or Cancel to skip this move.')) {<br />
ctx.statusElement.error("Move of fully protected page was aborted.");<br />
return;<br />
}<br />
}<br />
<br />
var moveToken = $(xml).find('page').attr('movetoken');<br />
if (!moveToken) {<br />
ctx.statusElement.error("Failed to retrieve move token.");<br />
return;<br />
}<br />
<br />
var query = {<br />
'action': 'move',<br />
'from': $(xml).find('page').attr('title'),<br />
'to': ctx.moveDestination,<br />
'token': moveToken,<br />
'reason': ctx.editSummary<br />
};<br />
if (ctx.moveTalkPage) {<br />
query.movetalk = 'true';<br />
}<br />
if (ctx.moveSubpages) {<br />
query.movesubpages = 'true'; // XXX don't know whether this works for non-admins<br />
}<br />
if (ctx.moveSuppressRedirect) {<br />
query.noredirect = 'true';<br />
}<br />
if (ctx.watchlistOption === 'watch') {<br />
query.watch = 'true';<br />
}<br />
<br />
ctx.moveProcessApi = new Wikipedia.api("moving page...", query, ctx.onMoveSuccess, ctx.statusElement, ctx.onMoveFailure);<br />
ctx.moveProcessApi.setParent(this);<br />
ctx.moveProcessApi.post();<br />
};<br />
<br />
var fnProcessDelete = function() {<br />
var xml = ctx.deleteApi.getXML();<br />
<br />
if ($(xml).find('page').attr('missing') === "") {<br />
ctx.statusElement.error("Cannot delete the page, because it no longer exists");<br />
return;<br />
}<br />
<br />
// extract protection info<br />
var editprot = $(xml).find('pr[type="edit"]');<br />
if (editprot.length > 0 && editprot.attr('level') === 'sysop' && !confirm('You are about to delete the fully protected page "' + ctx.pageName + <br />
(editprot.attr('expiry') === 'infinity' ? '" (protected indefinitely)' : ('" (protection expiring ' + editprot.attr('expiry') + ')')) +<br />
'. \n\nClick OK to proceed with the deletion, or Cancel to skip this deletion.')) {<br />
ctx.statusElement.error("Deletion of fully protected page was aborted.");<br />
return;<br />
}<br />
<br />
var deleteToken = $(xml).find('page').attr('deletetoken');<br />
if (!deleteToken) {<br />
ctx.statusElement.error("Failed to retrieve delete token.");<br />
return;<br />
}<br />
<br />
var query = {<br />
'action': 'delete',<br />
'title': $(xml).find('page').attr('title'),<br />
'token': deleteToken,<br />
'reason': ctx.editSummary<br />
};<br />
if (ctx.watchlistOption === 'watch') {<br />
query.watch = 'true';<br />
}<br />
<br />
ctx.deleteProcessApi = new Wikipedia.api("deleting page...", query, ctx.onDeleteSuccess, ctx.statusElement, ctx.onDeleteFailure);<br />
ctx.deleteProcessApi.setParent(this);<br />
ctx.deleteProcessApi.post();<br />
};<br />
<br />
var fnProcessProtect = function() {<br />
var xml = ctx.protectApi.getXML();<br />
<br />
if ($(xml).find('page').attr('missing') === "") {<br />
ctx.statusElement.error("Cannot protect the page, because it no longer exists");<br />
return;<br />
}<br />
<br />
var editprot = $(xml).find('pr[type="edit"]');<br />
// cascading protection not possible on edit<sysop<br />
// XXX fix this logic - I can't wrap my head around it<br />
//if (ctx.protectCascade && (editprot && editprot.attr('level') !== 'sysop') && (ctx.protectEdit && ctx.protectEdit.level !== 'sysop')) {<br />
// ctx.statusElement.error("Internal error: cascading protection requires sysop-level edit protection!");<br />
// return;<br />
//}<br />
<br />
var protectToken = $(xml).find('page').attr('protecttoken');<br />
if (!protectToken) {<br />
ctx.statusElement.error("Failed to retrieve protect token.");<br />
return;<br />
}<br />
<br />
var protections = '', expiry = '';<br />
if (ctx.protectEdit) {<br />
protections += 'edit=' + ctx.protectEdit.level;<br />
expiry += ctx.protectEdit.expiry;<br />
}<br />
if (ctx.protectMove) { <br />
if (ctx.protectEdit) {<br />
protections += '|';<br />
expiry += '|';<br />
}<br />
protections += 'move=' + ctx.protectMove.level;<br />
expiry += ctx.protectMove.expiry;<br />
}<br />
if (ctx.protectCreate) {<br />
if (ctx.protectEdit || ctx.protectMove) {<br />
protections += '|';<br />
expiry += '|';<br />
}<br />
protections += 'create=' + ctx.protectCreate.level;<br />
expiry += ctx.protectCreate.expiry;<br />
}<br />
var query = {<br />
action: 'protect',<br />
title: $(xml).find('page').attr('title'),<br />
token: protectToken,<br />
protections: protections,<br />
expiry: expiry,<br />
reason: ctx.editSummary<br />
};<br />
if (ctx.protectCascade) {<br />
query.cascade = 'true';<br />
}<br />
if (ctx.watchlistOption === 'watch') {<br />
query.watch = 'true';<br />
}<br />
<br />
ctx.protectProcessApi = new Wikipedia.api("protecting page...", query, ctx.onProtectSuccess, ctx.statusElement, ctx.onProtectFailure);<br />
ctx.protectProcessApi.setParent(this);<br />
ctx.protectProcessApi.post();<br />
};<br />
}; // end Wikipedia.page<br />
<br />
/** Wikipedia.page TODO: (XXX)<br />
* - Do we need the onFailure callbacks? How do we know when to call them? Timeouts? Enhance Wikipedia.api for failures?<br />
* - Should we retry loads also?<br />
* - Need to reset current action before the save?<br />
* - Deal with action.completed stuff<br />
* - Need to reset all parameters once done (e.g. edit summary, move destination, etc.)<br />
*/<br />
<br />
<br />
/**<br />
* **************** Wikipedia.wiki ****************<br />
* REMOVEME - but *only* after Twinkle no longer uses it<br />
*/<br />
<br />
/*<br />
currentAction: text, the current action (required)<br />
query: Object, the query (required)<br />
oninit: function, the function to call when page gotten (required)<br />
onsuccess: function, a function to call when post succeeded<br />
onerror: function, a function to call when we abort failed posts<br />
onretry: function, a function to call when we try to retry a post<br />
*/<br />
Wikipedia.wiki = function( currentAction, query, oninit, onsuccess, onerror, onretry ) {<br />
<br />
var node = document.createElement("div");<br />
node.style.background = "#F9F9F9";<br />
node.style.border = "1px solid maroon";<br />
node.style.padding = "0.6em 0.8em";<br />
node.style.margin = "0.5em";<br />
node.style.fontSize = "small";<br />
node.innerHTML = "<b>This user script is using the deprecated \"Wikipedia.wiki\" class to edit the wiki. " +<br />
"It may cease to function in the near future.</b><br />Please pass this message on to the script's maintainer, to ensure the script is upgraded.<br />" +<br />
"(The developers of Twinkle are happy to assist script maintainers with updating scripts.)";<br />
Status.warn(currentAction, [ node ]);<br />
<br />
this.currentAction = currentAction;<br />
this.query = query;<br />
this.oninit = oninit;<br />
this.onsuccess = onsuccess;<br />
this.onerror = onerror;<br />
this.onretry = onretry;<br />
this.statelem = new Status( currentAction );<br />
++Wikipedia.numberOfActionsLeft;<br />
};<br />
<br />
Wikipedia.wiki.prototype = {<br />
currentAction: '',<br />
onsuccess: null,<br />
onerror: null,<br />
onretry: null,<br />
oninit: null,<br />
query: null,<br />
postData: null,<br />
responseXML: null,<br />
statelem: null,<br />
counter: 0,<br />
post: function( data ) {<br />
this.postData = data;<br />
if( Wikipedia.editCount <= 0 ) {<br />
this.query['maxlag'] = wpMaxLag; // are we a bot?<br />
} else {<br />
--Wikipedia.editCount;<br />
}<br />
<br />
var xmlhttp = sajax_init_object();<br />
Wikipedia.dump.push( xmlhttp );<br />
xmlhttp.obj = this;<br />
xmlhttp.overrideMimeType('text/xml');<br />
xmlhttp.open( 'POST' , mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?useskin=monobook&' + QueryString.create( this.query ), true);<br />
xmlhttp.setRequestHeader('Content-type','application/x-www-form-urlencoded');<br />
xmlhttp.onerror = function(e) {<br />
this.obj.statelem.error( "Error " + this.status + " occurred while posting the document." );<br />
};<br />
xmlhttp.onload = function(e) {<br />
var self = this.obj;<br />
var status = this.status;<br />
if( status !== 200 ) {<br />
if( status === 503 ) {<br />
var retry = this.getResponseHeader( 'Retry-After' );<br />
var lag = this.getResponseHeader( 'X-Database-Lag' );<br />
if( lag ) {<br />
self.statelem.warn( "current lag of " + lag + " seconds is more than our defined maximum lag of " + wpMaxLag + " seconds, will retry in " + retry + " seconds" );<br />
window.setTimeout( function( self ) { self.post( self.postData ); }, retry * 1000, self );<br />
return;<br />
} else {<br />
self.statelem.error( "Error " + status + " occurred while posting the document." );<br />
}<br />
}<br />
return;<br />
}<br />
var xmlDoc;<br />
xmlDoc = self.responseXML = this.responseXML;<br />
var xpathExpr = 'boolean(//div[@class=\'previewnote\']/p/strong[contains(.,\'Sorry! We could not process your edit due to a loss of session data\')])';<br />
var nosession = xmlDoc.evaluate( xpathExpr, xmlDoc, null, XPathResult.BOOLEAN_TYPE, null ).booleanValue;<br />
if( nosession ) {<br />
// Grabbing the shipping token, and repost<br />
var new_token = xmlDoc.evaluate( '//input[@name="wfEditToken"]/@value', xmlDoc, null, XPathResult.STRING_TYPE, null ).stringValue;<br />
self.postData['wfEditToken'] = new_token;<br />
self.post( self.postData );<br />
} else {<br />
if( self.onsuccess ) {<br />
self.onsuccess( self );<br />
} else {<br />
var link = document.createElement( 'a' );<br />
link.setAttribute( 'href', mw.util.wikiGetlink(self.query['title']) );<br />
link.setAttribute( 'title', self.query['title'] );<br />
link.appendChild( document.createTextNode( self.query['title'] ) );<br />
<br />
self.statelem.info( [ 'completed (' , link , ')' ] );<br />
}<br />
Wikipedia.actionCompleted();<br />
}<br />
};<br />
xmlhttp.send( QueryString.create( this.postData ) );<br />
},<br />
get: function() {<br />
this.onloading( this );<br />
var redirect_query = {<br />
'action': 'query',<br />
'titles': this.query['title'],<br />
'redirects': ''<br />
};<br />
<br />
var wikipedia_api = new Wikipedia.api( "resolving eventual redirect", redirect_query, this.postget, this.statelem );<br />
wikipedia_api.parent = this;<br />
wikipedia_api.post();<br />
},<br />
// note: this was updated in April 2011 to work with the revamped Wikipedia.api class.<br />
// This was done so a lot of user scripts that use morebits didn't break down.<br />
// But some deprecated technologies, like XPath and sajax, were kept intentionally, to<br />
// discourage future consumers, and to not widen compatibility (i.e. doesn't work in IE9).<br />
postget: function(apiobj) {<br />
var xmlDoc = apiobj.getXML();<br />
var to = xmlDoc.evaluate( '//redirects/r/@to', xmlDoc, null, XPathResult.STRING_TYPE, null ).stringValue;<br />
if( !this.followRedirect ) {<br />
this.statelem.info('ignoring eventual redirect');<br />
} else if( to ) {<br />
this.query['title'] = to;<br />
}<br />
this.onloading( this );<br />
var xmlhttp = sajax_init_object();<br />
Wikipedia.dump.push( xmlhttp );<br />
xmlhttp.obj = this;<br />
xmlhttp.overrideMimeType('text/xml');<br />
xmlhttp.open( 'GET' , mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?useskin=monobook&' + QueryString.create( this.query ), true);<br />
xmlhttp.onerror = function() {<br />
this.obj.statelem.error( "Error " + this.status + " occurred while receiving the document." );<br />
};<br />
xmlhttp.onload = function() { <br />
this.obj.onloaded( this.obj );<br />
this.obj.responseXML = this.responseXML;<br />
this.obj.responseText = this.responseText;<br />
this.obj.oninit( this.obj ); <br />
};<br />
xmlhttp.send( null );<br />
},<br />
onloading: function() {<br />
this.statelem.status( 'loading data...' );<br />
},<br />
onloaded: function() {<br />
this.statelem.status( 'data loaded...' );<br />
}<br />
};<br />
<br />
<br />
/**<br />
* **************** Number ****************<br />
* REMOVEME - unused?<br />
*/<br />
<br />
Number.prototype.zeroFill = function( length ) {<br />
var str = this.toFixed();<br />
if( !length ) { return str; }<br />
while( str.length < length ) { str = '0' + str; }<br />
return str;<br />
};<br />
<br />
<br />
/**<br />
* **************** MediaWiki ****************<br />
* Wikitext manipulation<br />
*/<br />
<br />
var Mediawiki = {};<br />
<br />
Mediawiki.Template = {<br />
parse: function( text, start ) {<br />
var count = -1;<br />
var level = -1;<br />
var equals = -1;<br />
var current = '';<br />
var result = {<br />
name: '',<br />
parameters: {}<br />
};<br />
var key, value;<br />
<br />
for( var i = start; i < text.length; ++i ) {<br />
var test3 = text.substr( i, 3 );<br />
if( test3 === '{{{' ) {<br />
current += '{{{';<br />
i += 2;<br />
++level;<br />
continue;<br />
}<br />
if( test3 === '}}}' ) {<br />
current += '}}}';<br />
i += 2;<br />
--level;<br />
continue;<br />
}<br />
var test2 = text.substr( i, 2 );<br />
if( test2 === '{{' || test2 === '[[' ) {<br />
current += test2;<br />
++i;<br />
++level;<br />
continue;<br />
}<br />
if( test2 === '[[' ) {<br />
current += test2;<br />
++i;<br />
--level;<br />
continue;<br />
}<br />
if( test2 === '}}' ) {<br />
current += test2;<br />
++i;<br />
--level;<br />
<br />
if( level <= 0 ) {<br />
if( count === -1 ) {<br />
result.name = current.substring(2).trim();<br />
++count;<br />
} else {<br />
if( equals !== -1 ) {<br />
key = current.substring( 0, equals ).trim();<br />
value = current.substring( equals ).trim();<br />
result.parameters[key] = value;<br />
equals = -1;<br />
} else {<br />
result.parameters[count] = current;<br />
++count;<br />
}<br />
}<br />
break;<br />
}<br />
continue;<br />
}<br />
<br />
if( text.charAt(i) === '|' && level <= 0 ) {<br />
if( count === -1 ) {<br />
result.name = current.substring(2).trim();<br />
++count;<br />
} else {<br />
if( equals !== -1 ) {<br />
key = current.substring( 0, equals ).trim();<br />
value = current.substring( equals + 1 ).trim();<br />
result.parameters[key] = value;<br />
equals = -1;<br />
} else {<br />
result.parameters[count] = current;<br />
++count;<br />
}<br />
}<br />
current = '';<br />
} else if( equals === -1 && text.charAt(i) === '=' && level <= 0 ) {<br />
equals = current.length;<br />
current += text.charAt(i);<br />
} else {<br />
current += text.charAt(i);<br />
}<br />
}<br />
<br />
return result;<br />
}<br />
};<br />
<br />
Mediawiki.Page = function mediawikiPage( text ) {<br />
this.text = text;<br />
};<br />
<br />
Mediawiki.Page.prototype = {<br />
text: '',<br />
removeLink: function( link_target ) {<br />
var first_char = link_target.substr( 0, 1 );<br />
var link_re_string = "[" + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape( link_target.substr( 1 ), true );<br />
var link_simple_re = new RegExp( "\\[\\[:?(" + link_re_string + ")\\|?\\]\\]", 'g' );<br />
var link_named_re = new RegExp( "\\[\\[:?" + link_re_string + "\\|(.+?)\\]\\]", 'g' );<br />
if( link_simple_re.test(this.text) ) {<br />
this.text = this.text.replace( link_simple_re, "$1" );<br />
} else {<br />
this.text = this.text.replace( link_named_re, "$1" );<br />
}<br />
},<br />
commentOutImage: function( image, reason ) {<br />
var unbinder = new Unbinder( this.text );<br />
unbinder.unbind( '<!--', '-->' );<br />
<br />
reason = reason ? ' ' + reason + ': ' : '';<br />
var first_char = image.substr( 0, 1 );<br />
var image_re_string = "[" + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape( image.substr( 1 ), true ); <br />
<br />
/*<br />
* Check for normal image links, i.e. [[Image:Foobar.png|...]]<br />
* Will eat the whole link<br />
*/<br />
var links_re = new RegExp( "\\[\\[(?:[Ii]mage|[Ff]ile):\\s*" + image_re_string );<br />
var allLinks = unbinder.content.splitWeightedByKeys( '[[', ']]' ).uniq();<br />
for( var i = 0; i < allLinks.length; ++i ) {<br />
if( links_re.test( allLinks[i] ) ) {<br />
var replacement = '<!-- ' + reason + allLinks[i] + ' -->';<br />
unbinder.content = unbinder.content.replace( allLinks[i], replacement, 'g' );<br />
}<br />
}<br />
// unbind the newly created comments<br />
unbinder.unbind( '<!--', '-->' );<br />
<br />
/*<br />
* Check for gallery images, i.e. instances that must start on a new line, eventually preceded with some space, and must include Image: prefix<br />
* Will eat the whole line.<br />
*/<br />
var gallery_image_re = new RegExp( "(^\\s*(?:[Ii]mage|[Ff]ile):\\s*" + image_re_string + ".*?$)", 'mg' );<br />
unbinder.content.replace( gallery_image_re, "<!-- " + reason + "$1 -->" );<br />
<br />
// unbind the newly created comments<br />
unbinder.unbind( '<!--', '-->' );<br />
/*<br />
* Check free image usages, for example as template arguments, might have the Image: prefix excluded, but must be preceeded by an |<br />
* Will only eat the image name and the preceeding bar and an eventual named parameter<br />
*/<br />
var free_image_re = new RegExp( "(\\|\\s*(?:[\\w\\s]+\\=)?\\s*(?:(?:[Ii]mage|[Ff]ile):\\s*)?" + image_re_string + ")", 'mg' );<br />
unbinder.content.replace( free_image_re, "<!-- " + reason + "$1 -->" );<br />
<br />
// Rebind the content now, we are done!<br />
this.text = unbinder.rebind();<br />
},<br />
addToImageComment: function( image, data ) {<br />
var first_char = image.substr( 0, 1 );<br />
var image_re_string = "(?:[Ii]mage|[Ff]ile):\\s*[" + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape( image.substr( 1 ), true ); <br />
var links_re = new RegExp( "\\[\\[" + image_re_string );<br />
var allLinks = this.text.splitWeightedByKeys( '[[', ']]' ).uniq();<br />
for( var i = 0; i < allLinks.length; ++i ) {<br />
if( links_re.test( allLinks[i] ) ) {<br />
var replacement = allLinks[i];<br />
// just put it at the end?<br />
replacement = replacement.replace( /\]\]$/, '|' + data + ']]' );<br />
this.text = this.text.replace( allLinks[i], replacement, 'g' );<br />
}<br />
}<br />
var gallery_re = new RegExp( "^(\\s*" + image_re_string + '.*?)\\|?(.*?)$', 'mg' );<br />
var newtext = "$1|$2 " + data;<br />
this.text = this.text.replace( gallery_re, newtext );<br />
},<br />
removeTemplate: function( template ) {<br />
var first_char = template.substr( 0, 1 );<br />
var template_re_string = "(?:[Tt]emplate:)?\\s*[" + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape( template.substr( 1 ), true ); <br />
var links_re = new RegExp( "\\{\\{" + template_re_string );<br />
var allTemplates = this.text.splitWeightedByKeys( '{{', '}}', [ '{{{', '}}}' ] ).uniq();<br />
for( var i = 0; i < allTemplates.length; ++i ) {<br />
if( links_re.test( allTemplates[i] ) ) {<br />
this.text = this.text.replace( allTemplates[i], '', 'g' );<br />
}<br />
}<br />
},<br />
getText: function() {<br />
return this.text;<br />
}<br />
};<br />
<br />
<br />
/**<br />
* **************** isInNetwork(), isIPAddress() ****************<br />
*/<br />
<br />
// ipadress is in the format 1.2.3.4 and network is in the format 1.2.3.4/5<br />
function isInNetwork( ipaddress, network ) {<br />
var iparr = ipaddress.split('.');<br />
var ip = (parseInt(iparr[0], 10) << 24) + (parseInt(iparr[1], 10) << 16) + (parseInt(iparr[2], 10) << 8) + (parseInt(iparr[3], 10));<br />
<br />
var netmask = 0xffffffff << network.split('/')[1];<br />
<br />
var netarr = network.split('/')[0].split('.');<br />
var net = (parseInt(netarr[0], 10) << 24) + (parseInt(netarr[1], 10) << 16) + (parseInt(netarr[2], 10) << 8) + (parseInt(netarr[3], 10));<br />
<br />
return (ip & netmask) === net;<br />
}<br />
<br />
// Returns true if given string contains a valid IP-address, that is, from 0.0.0.0 to 255.255.255.255<br />
function isIPAddress( string ){<br />
var res = /(\d{1,4})\.(\d{1,3})\.(\d{1,3})\.(\d{1,4})/.exec( string );<br />
return res && res.slice( 1, 5 ).every( function( e ) { return e < 256; } );<br />
}<br />
<br />
<br />
/**<br />
* **************** QueryString ****************<br />
* Maps the querystring to an object<br />
*<br />
* Functions:<br />
*<br />
* QueryString.exists(key)<br />
* returns true if the particular key is set<br />
* QueryString.get(key)<br />
* returns the value associated to the key<br />
* QueryString.equals(key, value)<br />
* returns true if the value associated with given key equals given value<br />
* QueryString.toString()<br />
* returns the query string as a string<br />
* QueryString.create( hash )<br />
* creates an querystring and encodes strings via encodeURIComponent and joins arrays with | <br />
*<br />
* In static context, the value of location.search.substring(1), else the value given to the constructor is going to be used. The mapped hash is saved in the object.<br />
*<br />
* Example:<br />
*<br />
* var value = QueryString.get('key');<br />
* var obj = new QueryString('foo=bar&baz=quux');<br />
* value = obj.get('foo');<br />
*/<br />
var QueryString = function(qString) {<br />
this.string = qString;<br />
this.params = {};<br />
<br />
if( !qString.length ) {<br />
return;<br />
}<br />
<br />
qString.replace(/\+/, ' ');<br />
var args = qString.split('&');<br />
<br />
for( var i = 0; i < args.length; ++i ) {<br />
var pair = args[i].split( '=' );<br />
var key = decodeURIComponent( pair[0] ), value = key;<br />
<br />
if( pair.length === 2 ) {<br />
value = decodeURIComponent( pair[1] );<br />
}<br />
<br />
this.params[key] = value;<br />
}<br />
};<br />
<br />
QueryString.staticstr = null;<br />
<br />
QueryString.staticInit = function() {<br />
if( !QueryString.staticstr ) {<br />
QueryString.staticstr = new QueryString(location.search.substring(1));<br />
}<br />
};<br />
<br />
QueryString.get = function(key) {<br />
QueryString.staticInit();<br />
return QueryString.staticstr.get(key);<br />
};<br />
<br />
QueryString.prototype.get = function(key) {<br />
return this.params[key] ? this.params[key] : null;<br />
};<br />
<br />
QueryString.exists = function(key) {<br />
QueryString.staticInit();<br />
return QueryString.staticstr.exists(key);<br />
};<br />
<br />
QueryString.prototype.exists = function(key) {<br />
return this.params[key] ? true : false;<br />
};<br />
<br />
QueryString.equals = function(key, value) {<br />
QueryString.staticInit();<br />
return QueryString.staticstr.equals(key, value);<br />
};<br />
<br />
QueryString.prototype.equals = function(key, value) {<br />
return this.params[key] === value ? true : false;<br />
};<br />
<br />
QueryString.toString = function() {<br />
QueryString.staticInit();<br />
return QueryString.staticstr.toString();<br />
};<br />
<br />
QueryString.prototype.toString = function() {<br />
return this.string ? this.string : null;<br />
};<br />
<br />
QueryString.create = function( arr ) {<br />
var resarr = [];<br />
var editToken; // KLUGE: this should always be the last item in the query string (bug TW-B-0013)<br />
for( var i in arr ) {<br />
if( typeof arr[i] === 'undefined' ) {<br />
continue;<br />
}<br />
var res;<br />
if( arr[i] instanceof Array ){<br />
var v = [];<br />
for(var j = 0; j < arr[i].length; ++j ) {<br />
v[j] = encodeURIComponent( arr[i][j] );<br />
}<br />
res = v.join('|');<br />
} else {<br />
res = encodeURIComponent( arr[i] );<br />
}<br />
if( i === 'wpEditToken' ) {<br />
editToken = res;<br />
} else {<br />
resarr.push( encodeURIComponent( i ) + '=' + res );<br />
}<br />
}<br />
if( typeof editToken !== 'undefined' ) {<br />
resarr.push( 'wpEditToken=' + editToken );<br />
}<br />
return resarr.join('&');<br />
};<br />
QueryString.prototype.create = QueryString.create;<br />
<br />
/**<br />
* **************** Exception ****************<br />
* Simple exception handling<br />
* REMOVEME - unused?<br />
*/<br />
<br />
var Exception = function( message ) {<br />
this.message = message || '';<br />
this.name = "Exception";<br />
};<br />
<br />
Exception.prototype.what = function() {<br />
return this.message;<br />
};<br />
<br />
<br />
/**<br />
* **************** Status ****************<br />
*/<br />
<br />
var Status = function( text, stat, type ) {<br />
this.text = this.codify(text);<br />
this.stat = this.codify(stat);<br />
this.type = type || 'status';<br />
if (type === 'error') {<br />
// hack to force the page not to reload when an error is output - see also update() below<br />
Wikipedia.numberOfActionsLeft = 1000;<br />
// call error callback<br />
if (Status.errorEvent) {<br />
Status.errorEvent();<br />
}<br />
}<br />
this.generate(); <br />
if( stat ) {<br />
this.render();<br />
}<br />
};<br />
<br />
Status.init = function( root ) {<br />
if( !( root instanceof Element ) ) {<br />
throw new Error( 'object not an instance of Element' );<br />
}<br />
while( root.hasChildNodes() ) {<br />
root.removeChild( root.firstChild );<br />
}<br />
Status.root = root;<br />
Status.errorEvent = null;<br />
};<br />
<br />
Status.root = null;<br />
<br />
Status.onError = function( handler ) {<br />
if (typeof handler === "function") {<br />
Status.errorEvent = handler;<br />
} else {<br />
throw "Status.onError: handler is not a function";<br />
}<br />
};<br />
<br />
Status.prototype = {<br />
stat: null,<br />
text: null,<br />
type: 'status',<br />
target: null,<br />
node: null,<br />
linked: false,<br />
link: function() {<br />
if( ! this.linked && Status.root ) {<br />
Status.root.appendChild( this.node );<br />
this.linked = true;<br />
}<br />
},<br />
unlink: function() {<br />
if( this.linked ) {<br />
Status.root.removeChild( this.node );<br />
this.linked = false;<br />
}<br />
},<br />
codify: function( obj ) {<br />
if ( ! ( obj instanceof Array ) ) {<br />
obj = [ obj ];<br />
}<br />
var result;<br />
result = document.createDocumentFragment();<br />
for( var i = 0; i < obj.length; ++i ) {<br />
if( typeof obj[i] === 'string' ) {<br />
result.appendChild( document.createTextNode( obj[i] ) );<br />
} else if( obj[i] instanceof Element ) {<br />
result.appendChild( obj[i] );<br />
} // Else cosmic radiation made something shit<br />
}<br />
return result;<br />
<br />
},<br />
update: function( status, type ) {<br />
this.stat = this.codify( status );<br />
if( type ) {<br />
this.type = type;<br />
if (type === 'error') {<br />
// hack to force the page not to reload when an error is output - see also Status() above<br />
Wikipedia.numberOfActionsLeft = 1000;<br />
// call error callback<br />
if (Status.errorEvent) {<br />
Status.errorEvent();<br />
}<br />
}<br />
}<br />
this.render();<br />
},<br />
generate: function() {<br />
this.node = document.createElement( 'div' );<br />
this.node.appendChild( document.createElement('span') ).appendChild( this.text );<br />
this.node.appendChild( document.createElement('span') ).appendChild( document.createTextNode( ': ' ) );<br />
this.target = this.node.appendChild( document.createElement( 'span' ) );<br />
this.target.appendChild( document.createTextNode( '' ) ); // dummy node<br />
},<br />
render: function() {<br />
this.node.className = 'tw_status_' + this.type;<br />
while( this.target.hasChildNodes() ) {<br />
this.target.removeChild( this.target.firstChild );<br />
}<br />
this.target.appendChild( this.stat );<br />
this.link();<br />
},<br />
status: function( status ) {<br />
this.update( status, 'status');<br />
},<br />
info: function( status ) {<br />
this.update( status, 'info');<br />
},<br />
warn: function( status ) {<br />
this.update( status, 'warn');<br />
},<br />
error: function( status ) {<br />
this.update( status, 'error');<br />
}<br />
};<br />
<br />
Status.status = function( text, status ) {<br />
return new Status( text, status, 'status' );<br />
};<br />
<br />
Status.info = function( text, status ) {<br />
return new Status( text, status, 'info' );<br />
};<br />
<br />
Status.warn = function( text, status ) {<br />
return new Status( text, status, 'warn' );<br />
};<br />
<br />
Status.error = function( text, status ) {<br />
return new Status( text, status, 'error' );<br />
};<br />
<br />
<br />
/**<br />
* **************** htmlNode() ****************<br />
* Simple helper function to create a simple node<br />
* XXX rewrite more flexibly, and place under an object, for example QuickNode.create<br />
*/<br />
<br />
function htmlNode( type, content, color ) {<br />
var node = document.createElement( type );<br />
if( color ) {<br />
node.style.color = color;<br />
}<br />
node.appendChild( document.createTextNode( content ) );<br />
return node;<br />
}<br />
<br />
<br />
/**<br />
* **************** SimpleWindow ****************<br />
* A simple draggable window<br />
* now a wrapper for jQuery UI's dialog feature<br />
*/<br />
<br />
// The height passed in here is the maximum allowable height for the content area.<br />
var SimpleWindow = function( width, height ) {<br />
var content = document.createElement( 'div' );<br />
this.content = content;<br />
content.className = 'morebits-dialog-content';<br />
<br />
this.height = height;<br />
<br />
$(this.content).dialog({<br />
autoOpen: false,<br />
buttons: { "Placeholder button": function() {} },<br />
dialogClass: 'morebits-dialog',<br />
width: Math.min(parseInt(window.innerWidth, 10), parseInt(width ? width : 800, 10)),<br />
// give jQuery the given height value (which represents the anticipated height of the dialog) here, so<br />
// it can position the dialog appropriately<br />
// the 20 pixels represents adjustment for the extra height of the jQuery dialog "chrome", compared<br />
// to that of the old SimpleWindow<br />
height: height + 20<br />
}).bind("dialogresize", function(event, ui) {<br />
this.style.maxHeight = "";<br />
});<br />
<br />
var $widget = $(this.content).dialog("widget");<br />
<br />
// add background gradient to titlebar<br />
var $titlebar = $widget.find(".ui-dialog-titlebar");<br />
var oldstyle = $titlebar.attr("style");<br />
$titlebar.attr("style", (oldstyle ? oldstyle : "") + '; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB%2FqqA%2BAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEhQTFRFr73ZobTPusjdsMHZp7nVwtDhzNbnwM3fu8jdq7vUt8nbxtDkw9DhpbfSvMrfssPZqLvVztbno7bRrr7W1d%2Fs1N7qydXk0NjpkW7Q%2BgAAADVJREFUeNoMwgESQCAAAMGLkEIi%2FP%2BnbnbpdB59app5Vdg0sXAoMZCpGoFbK6ciuy6FX4ABAEyoAef0BXOXAAAAAElFTkSuQmCC) !important;');<br />
<br />
// delete the placeholder button (it's only there so the buttonpane gets created)<br />
$widget.find("button").each(function(key, value) {<br />
value.parentNode.removeChild(value);<br />
});<br />
<br />
// add container for the buttons we add, and the footer links (if any)<br />
var buttonspan = document.createElement("span");<br />
buttonspan.className = "morebits-dialog-buttons";<br />
var linksspan = document.createElement("span");<br />
linksspan.className = "morebits-dialog-footerlinks";<br />
$widget.find(".ui-dialog-buttonpane").append(buttonspan, linksspan);<br />
};<br />
<br />
SimpleWindow.prototype = {<br />
buttons: [],<br />
height: 600,<br />
hasFooterLinks: false,<br />
scriptName: null,<br />
<br />
// Focuses the dialog. This might work, or on the contrary, it might not.<br />
focus: function(event) {<br />
$(this.content).dialog("moveToTop");<br />
},<br />
// Closes the dialog. If this is set as an event handler, it will stop the event from doing anything more.<br />
close: function(event) {<br />
if (event) {<br />
event.preventDefault();<br />
}<br />
$(this.content).dialog("close");<br />
},<br />
// Shows the dialog. Calling display() on a dialog that has previously been closed might work, but it is not guaranteed.<br />
display: function() {<br />
if (this.scriptName) {<br />
var $widget = $(this.content).dialog("widget");<br />
$widget.find(".morebits-dialog-scriptname").remove();<br />
var scriptnamespan = document.createElement("span");<br />
scriptnamespan.className = "morebits-dialog-scriptname";<br />
scriptnamespan.textContent = this.scriptName + " \u00B7 "; // U+00B7 MIDDLE DOT = &middot;<br />
$widget.find(".ui-dialog-title").prepend(scriptnamespan);<br />
}<br />
<br />
var dialog = $(this.content).dialog("open");<br />
if (window.setupTooltips) { dialog.parent()[0].ranSetupTooltipsAlready = false; setupTooltips(dialog.parent()[0]); } //tie in with NAVPOP<br />
this.setHeight( this.height ); // init height algorithm<br />
},<br />
// Sets the dialog title.<br />
setTitle: function( title ) {<br />
$(this.content).dialog("option", "title", title);<br />
},<br />
// Sets the script name, appearing as a prefix to the title to help users determine which<br />
// user script is producing which dialog. For instance, Twinkle modules set this to "Twinkle".<br />
setScriptName: function( name ) {<br />
this.scriptName = name;<br />
},<br />
// Sets the dialog width.<br />
setWidth: function( width ) {<br />
$(this.content).dialog("option", "width", width);<br />
},<br />
// Sets the dialog's maximum height. The dialog will auto-size to fit its contents,<br />
// but the content area will grow no larger than the height given here.<br />
setHeight: function( height ) {<br />
this.height = height;<br />
<br />
// from display time onwards, let the browser determine the optimum height, and instead limit the height at the given value<br />
// note that the given height will exclude the approx. 20px that the jQuery UI chrome has in height in addition to the height<br />
// of an equivalent "classic" SimpleWindow<br />
if (parseInt(getComputedStyle($(this.content).dialog("widget")[0], null).height, 10) > window.innerHeight) {<br />
$(this.content).dialog("option", "height", window.innerHeight - 2).dialog("option", "position", "top");<br />
} else {<br />
$(this.content).dialog("option", "height", "auto");<br />
}<br />
$(this.content).dialog("widget").find(".morebits-dialog-content")[0].style.maxHeight = parseInt(this.height - 30, 10) + "px";<br />
},<br />
// Sets the content of the dialog to the given element node, usually from rendering a QuickForm or QuickForm element.<br />
// Re-enumerates the footer buttons, but leaves the footer links as they are.<br />
// Be sure to call this at least once before the dialog is displayed...<br />
setContent: function( content ) {<br />
this.purgeContent();<br />
this.addContent( content );<br />
},<br />
addContent: function( content ) {<br />
this.content.appendChild( content );<br />
<br />
// look for submit buttons in the content, hide them, and add a proxy button to the button pane<br />
var thisproxy = this;<br />
$(this.content).find('input[type="submit"], button[type="submit"]').each(function(key, value) {<br />
value.style.display = "none";<br />
var button = document.createElement("button");<br />
button.textContent = (value.hasAttribute("value") ? value.getAttribute("value") : (value.textContent ? value.textContent : "Submit Query"));<br />
// here is an instance of cheap coding, probably a memory-usage hit in using a closure here<br />
button.addEventListener("click", function() { value.click(); }, false);<br />
thisproxy.buttons.push(button);<br />
});<br />
// remove all buttons from the button pane and re-add them<br />
if (this.buttons.length > 0) {<br />
$(this.content).dialog("widget").find(".morebits-dialog-buttons").empty().append(this.buttons)[0].removeAttribute("data-empty");<br />
} else {<br />
$(this.content).dialog("widget").find(".morebits-dialog-buttons")[0].setAttribute("data-empty", "data-empty"); // used by CSS<br />
}<br />
},<br />
purgeContent: function( content ) {<br />
this.buttons = [];<br />
// delete all buttons in the buttonpane<br />
$(this.content).dialog("widget").find(".morebits-dialog-buttons").empty();<br />
<br />
while( this.content.hasChildNodes() ) {<br />
this.content.removeChild( this.content.firstChild );<br />
}<br />
},<br />
// Adds a link in the bottom-right corner of the dialog.<br />
// This can be used to provide help or policy links.<br />
// For example, Twinkle's CSD module adds a link to the CSD policy page,<br />
// as well as a link to Twinkle's documentation.<br />
addFooterLink: function( text, wikiPage ) {<br />
var $footerlinks = $(this.content).dialog("widget").find(".morebits-dialog-footerlinks");<br />
if (this.hasFooterLinks) {<br />
var bullet = document.createElement("span");<br />
bullet.textContent = " \u2022 "; // U+2022 BULLET<br />
$footerlinks.append(bullet);<br />
}<br />
var link = document.createElement("a");<br />
link.setAttribute("href", mw.util.wikiGetlink(wikiPage) );<br />
link.setAttribute("title", wikiPage);<br />
link.setAttribute("target", "_blank");<br />
link.textContent = text;<br />
$footerlinks.append(link);<br />
this.hasFooterLinks = true;<br />
},<br />
moveWindow: function( x, y ) {<br />
// unimplemented<br />
alert("SimpleWindow.moveWindow is no longer implemented.");<br />
},<br />
resizeWindow: function( x, y ) {<br />
// unimplemented<br />
alert("SimpleWindow.resizeWindow is no longer implemented.");<br />
}<br />
};<br />
<br />
// Enables or disables all footer buttons on all SimpleWindows in the current page.<br />
// This should be called with |false| when the button(s) become irrelevant (e.g. just before Status.init is called).<br />
// This is not an instance method so that consumers don't have to keep a reference to the original<br />
// SimpleWindow object sitting around somewhere. Anyway, most of the time there will only be one<br />
// SimpleWindow open, so this shouldn't matter.<br />
SimpleWindow.setButtonsEnabled = function( enabled ) {<br />
$(".morebits-dialog-buttons button").attr("disabled", !enabled);<br />
};<br />
<br />
<br />
<br />
// Twinkle blacklist was removed per consensus at http://en.wikipedia.org/wiki/Wikipedia:Administrators%27_noticeboard/Archive221#New_Twinkle_blacklist_proposal<br />
<br />
<br />
// </nowiki></div>Exor674//wiki.dreamwidth.net/wiki/index.php/Database_Table:_usermsgproplistDatabase Table: usermsgproplist2011-12-18T06:02:33Z<p>Exor674: </p>
<hr />
<div>{{Database Table|name=usermsgproplist|table type=global|need desc=1|repo=dw-free}}<br />
<br />
You can see the props that get populated into this table at [[Proplists/usermsgprop]].<br />
<br />
= Definition =<br />
<source lang="sql"><br />
CREATE TABLE usermsgproplist (<br />
propid SMALLINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,<br />
name VARCHAR(255) DEFAULT NULL,<br />
des VARCHAR(255) DEFAULT NULL,<br />
<br />
UNIQUE KEY (name)<br />
)<br />
</source><br />
{{Database Table Footer}}</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Database_Table:_pollproplist2Database Table: pollproplist22011-12-18T06:02:06Z<p>Exor674: </p>
<hr />
<div>{{Database Table|name=pollproplist2|table type=global|repo=dw-free}}<br />
[[Description::This table stores metadata of poll properties used.]]<br />
<br />
You can see the props that get populated into this table at [[Proplists/pollprop]].<br />
<br />
= Definition =<br />
<source lang="sql"><br />
CREATE TABLE pollproplist2 (<br />
propid SMALLINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,<br />
name VARCHAR(255) DEFAULT NULL,<br />
des VARCHAR(255) DEFAULT NULL,<br />
scope ENUM('general', 'local') DEFAULT 'general' NOT NULL,<br />
<br />
UNIQUE KEY (name)<br />
)<br />
</source><br />
{{Database Table Footer}}</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Database_Table:_talkproplistDatabase Table: talkproplist2011-12-18T06:01:34Z<p>Exor674: </p>
<hr />
<div>{{Database Table|name=talkproplist|table type=global|need desc=1|repo=dw-free}}<br />
<br />
You can see the props that get populated into this table at [[Proplists/talkprop]].<br />
<br />
= Definition =<br />
<source lang="sql"><br />
CREATE TABLE talkproplist (<br />
tpropid smallint(5) unsigned NOT NULL auto_increment,<br />
name varchar(50) default NULL,<br />
prettyname varchar(60) default NULL,<br />
datatype enum('char','num','bool') NOT NULL default 'char',<br />
scope enum('general', 'local') NOT NULL default 'general',<br />
ownership ENUM('system', 'user') NOT NULL default 'user',<br />
des varchar(255) default NULL,<br />
<br />
PRIMARY KEY (tpropid),<br />
UNIQUE KEY name (name)<br />
)<br />
</source><br />
{{Database Table Footer}}</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Database_Table:_logproplistDatabase Table: logproplist2011-12-18T06:01:18Z<p>Exor674: </p>
<hr />
<div>{{Database Table|name=logproplist|table type=global|need desc=1|repo=dw-free}}<br />
<br />
You can see the props that get populated into this table at [[Proplists/logprop]].<br />
= Definition =<br />
<source lang="sql"><br />
CREATE TABLE logproplist (<br />
propid tinyint(3) unsigned NOT NULL auto_increment,<br />
name varchar(50) default NULL,<br />
prettyname varchar(60) default NULL,<br />
sortorder mediumint(8) unsigned default NULL,<br />
datatype enum('char','num','bool') NOT NULL default 'char',<br />
scope enum('general', 'local') NOT NULL default 'general',<br />
ownership ENUM('system', 'user') NOT NULL default 'user',<br />
des varchar(255) default NULL,<br />
<br />
PRIMARY KEY (propid),<br />
UNIQUE KEY name (name)<br />
)<br />
</source><br />
{{Database Table Footer}}</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Database_Table:_userproplistDatabase Table: userproplist2011-12-18T06:00:22Z<p>Exor674: </p>
<hr />
<div>{{Database Table|name=userproplist|table type=global|need desc=1|repo=dw-free}}<br />
<br />
You can see the props that get populated into this table at [[Proplists/userprop]].<br />
<br />
= Definition =<br />
<source lang="sql"><br />
CREATE TABLE userproplist (<br />
upropid smallint(5) unsigned NOT NULL auto_increment,<br />
name varchar(50) default NULL,<br />
indexed enum('1','0') NOT NULL default '1',<br />
prettyname varchar(60) default NULL,<br />
datatype enum('char','num','bool') NOT NULL default 'char',<br />
des varchar(255) default NULL,<br />
<br />
PRIMARY KEY (upropid),<br />
UNIQUE KEY name (name)<br />
)<br />
</source><br />
{{Database Table Footer}}</div>Exor674//wiki.dreamwidth.net/wiki/index.php/ProplistsProplists2011-12-18T05:58:54Z<p>Exor674: Created page with "Category:Administration Lists of properties ( props ) for storing various extra information for certain items. * User props * [[Proplists/logprop|Log ..."</p>
<hr />
<div>[[Category:Administration]]<br />
Lists of properties ( props ) for storing various extra information for certain items.<br />
<br />
* [[Proplists/userprop|User props]]<br />
* [[Proplists/logprop|Log props]] ( e.g. Entry props )<br />
* [[Proplists/talkprop|Talk props]] ( e.g. Comment props )<br />
* [[Proplists/pollprop|Poll props]]<br />
* [[Proplists/usermsgprop|User Message props]]</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Proplists/usermsgpropProplists/usermsgprop2011-12-18T05:56:34Z<p>Exor674: </p>
<hr />
<div>[[Category:Administration|Usermsgprop]]<br />
This is a list of all the currently defined and in-use usermsgprops.<br />
<br />
These are defined in the table {{DBTable|usermsgproplist}} and the values are stored in {{DBTable|usermsgprop}}.<br />
<br />
<!-- DO NOT EDIT ANYTHING BELOW THIS LINE. ANY EDITS WILL BE CLOBBERED BY Exor674Bot --><br />
{| class="wikitable"<br />
|-<br />
! Property<br />
! Description<br />
|-<br />
| userpic<br />
| Userpic chosen by the user who created the message<br />
|}</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Proplists/pollpropProplists/pollprop2011-12-18T05:55:50Z<p>Exor674: </p>
<hr />
<div>[[Category:Administration|Pollprop]]<br />
This is a list of all the currently defined and in-use pollprops.<br />
<br />
These are defined in the table {{DBTable|pollproplist2}} and the values are stored in {{DBTable|pollprop2}}.<br />
<br />
<!-- DO NOT EDIT ANYTHING BELOW THIS LINE. ANY EDITS WILL BE CLOBBERED BY Exor674Bot --><br />
{| class="wikitable"<br />
|-<br />
! Property<br />
! Description<br />
|-<br />
| createdate<br />
| The voter must have created their account by the date specified (Pacific time) in order to vote. Must be in format YYYY-MM-DD, otherwise there will be no restriction.<br />
|-<br />
| unique<br />
| If set to 1, then a person with a given email address can only vote once in the poll<br />
|}</div>Exor674//wiki.dreamwidth.net/wiki/index.php/Proplists/logpropProplists/logprop2011-12-18T05:54:59Z<p>Exor674: </p>
<hr />
<div>[[Category:Administration|Logprop]]<br />
This is a list of all the currently defined and in-use logprops ( e.g. entryprops ).<br />
<br />
These are defined in the table {{DBTable|logproplist}} and the values are stored in {{DBTable|logprop2}}.<br />
<br />
<!-- DO NOT EDIT ANYTHING BELOW THIS LINE. ANY EDITS WILL BE CLOBBERED BY Exor674Bot --><br />
{| class="wikitable"<br />
|-<br />
! Property<br />
! Description<br />
|-<br />
| adult_content<br />
| Sets the entry as containing adult content (none, explicit, concepts)<br />
|-<br />
| adult_content_maintainer<br />
| Sets the entry as containing adult content (none, explicit, concepts) -- community maintainer override<br />
|-<br />
| adult_content_maintainer_reason<br />
| The reason that the entry is marked as containing adult content -- community maintainer override<br />
|-<br />
| adult_content_reason<br />
| The reason that the entry is marked as containing adult content<br />
|-<br />
| commentalter<br />
| Unix time of the last change to number of comments to this post.<br />
|-<br />
| current_coords<br />
| Current coordinates at time of post, in form '45.2935N 123.3452W'<br />
|-<br />
| current_location<br />
| Current location at time of post, free form text<br />
|-<br />
| current_mood<br />
| Your current mood.<br />
|-<br />
| current_moodid<br />
| Your current mood ID number, if known.<br />
|-<br />
| current_music<br />
| Music you're currently listening to.<br />
|-<br />
| hasscreened<br />
| True if comments to this item include screened comments<br />
|-<br />
| import_source<br />
| String describing where this entry was imported from<br />
|-<br />
| interface<br />
| String describing how this entry was posted<br />
|-<br />
| opt_backdated<br />
| Set to true if this item shouldn't show up on people's friends lists (because it occurred in the past)<br />
|-<br />
| opt_nocomments<br />
| Turn on if readers can't post comments on this entry.<br />
|-<br />
| opt_nocomments_maintainer<br />
| Disables comments on this entry -- community maintainer override.<br />
|-<br />
| opt_noemail<br />
| Turn on if the poster isn't interested in receiving comments to this post by email<br />
|-<br />
| opt_preformatted<br />
| Turn on if post contains HTML and shouldn't be formatted<br />
|-<br />
| opt_screening<br />
| Like opt_whoscreened: A = All, R = Remote needed (anonymous only), F = non-Friends, N = None, else use userprop.<br />
|-<br />
| picture_keyword<br />
| A keyword that should align to a defined picture<br />
|-<br />
| picture_mapid<br />
| A keyword that should align to a mapid<br />
|-<br />
| revnum<br />
| Number of times this post has been edited.<br />
|-<br />
| revtime<br />
| Unix time of the last edit<br />
|-<br />
| statusvis<br />
| 'V' or undef for visible, 'S' for suspended<br />
|-<br />
| syn_id<br />
| Unique id of syndication item<br />
|-<br />
| syn_link<br />
| Original URL of syndication item<br />
|-<br />
| taglist<br />
| Comma separated list of tags on the entry<br />
|-<br />
| unknown8bit<br />
| True if text has 8-bit data that's not in UTF-8<br />
|-<br />
| unsuspend_supportid<br />
| The support request ID of the unsuspension request submitted by a user whose entry was suspended. Undef or 0 if no request is currently open.<br />
|-<br />
| used_rte<br />
| True if entry was composed using the rich text editor<br />
|-<br />
| useragent<br />
| Name of web/mobile/sip/etc client used to post<br />
|-<br />
| xpost<br />
| Maps to crossposts of this entry on other sites<br />
|-<br />
| xpostdetail<br />
| Maps to crossposts of this entry on other sites<br />
|}</div>Exor674