Compare commits

..

1 commit

Author SHA1 Message Date
Alexey Verkhovsky
98848397d3 [DRASTIC THING!] Moving trunkto branches/instiki-madeleine, for posterity 2005-11-06 08:19:30 +00:00
33474 changed files with 27553 additions and 547196 deletions

853
CHANGELOG Normal file → Executable file
View file

@ -1,704 +1,189 @@
N.B.: You *must* run * trunk:
Fixed --daemon option.
ruby bundle
ruby bundle exec rake upgrade_instiki
after installing the new software, to enjoy the benefits of this new version.
------------------------------------------------------------------------------
* 0.19.3
New Features:
* Source view for Revisions
* Rails updated to 2.3.14 (Security)
* itextomml updated to 1.4.6
* Replace REXML with Nokogiri in Maruku and
in xhtml_safe_sanitize().
(Huge speedup in rendering long pages)
* MathJax updated to 1.1a final
Bugs Fixed:
* Bundler upgraded to 1.0.18
* Fix null search bug
* Better text/html serialization (thank you, Nokogiri)
* Fix Maruku footnote backlink (reported by Shamaoke)
* Fix Maruku link bug
* Fix Maruku image title bug
* Fix Maruku hrule, email address and header bugs
* Fix Maruku bold-in-italics bug
* Fix Maruku empty list-item bug
------------------------------------------------------------------------------
* 0.19.2
New Features:
* MathJax rendering for non-MathML capable browsers.
* RedCloth (Textile) upgraded to 4.x (now handled
by Bundler).
* Bundler upgraded to 1.0.7
* Rails updated to 2.3.11
Bugs Fixed:
* Redirects and categories of included pages should
not be inherited. (Suggestion of Andrew Stacey).
* Bug in Maruku equation handling (reported by Andrew Stacey).
* SVG-Edit updates and bug-fixes.
* Bug in editing S5 slideshows.
* Unvendor Rack
* Fix Maruku list-parsing bug (reported by Shamaoke)
* Validate Web address (Reported by Richard Marquez).
* Fix a well-formedness bug
------------------------------------------------------------------------------
* 0.19.1
New Features:
* From the "All" or Category listings, you can export selected pages (in any
desired order) to a single LaTeX file.
* LaTeX export supports \array{} (with no options) and a LaTeX-style optional
argument for \sqrt[]{}. The latter requires itextomml 1.4.5 or later.
* Updated to itextomml 1.4.5 (a bunch of new itex language features).
* Updated to Rails 2.3.10. (There were security issues in 2.3.9 which, happily,
did NOT affect Instiki 0.19. But 2.3.10 has other improvements, as well.)
Bugs Fixed:
* Several SVG-Edit bugs fixed.
* Removed some superfluous junk, to slim down the distribution (cuts the size of
the compressed .tar.gz nearly in half).
------------------------------------------------------------------------------
* 0.19
New Features:
* WYSIWYG SVG editing (via SVG-edit)
* One-click S5 templates
* Itex2MML is now a Rubygem. Latest is itextomml-1.4.2.
* Rails Metal itex endpoint
* HTML5 support
* Support IALs on Markdown list items
* Updated to Rails 2.3.9 and Erubis (now at 2.6.6)
* Updated for Rack 1.2.1, sqlite3-ruby 1.3.1
* Manages dependencies using Bundler. Before running Instiki for the first time
(and whenever you update), run
ruby bundle
rake upgrade_instiki
from the instiki directory. (You may need to run
ruby bundle exec rake upgrade_instiki
instead, if you get a complaint about your version of rake.)
Bugs Fixed:
* Works with Ruby 1.9.2
* Fixed a bug in non-Latin WikiWord processing. (Reported by Alexander Hambug)
* Fixed Cyrillic WikiWord support.
* More informative dnsbl lookup responses (suggested by Toby Bartels)
* Fixed a bug in LaTeX output
* No longer conflicts with sqlite3-ruby 1.3.x Rubygem
* Fixed some Category listing bugs
* Fixed an escaping bug in 'new' and 'edit' templates. (Reported by Toby Bartels)
* Allow special characters ('.', '/', etc) in page names.
* Fix BlahTeX/PNG path, so equations render in diff and
previous revision pages.
* Fix HTML Export feature so that uploaded files are
included, stylesheets load, etc.
* Uploaded files inclided in Markup Export.
* Fix Print View, so that uploaded images work.
* Fix some more Ruby 1.9 isues.
* Prevent page from being renamed to null.
* Fix Migration to work under PostgreSQL (from J. Zellman).
* Updated vendored plugins
------------------------------------------------------------------------------
* 0.18.1
New Features:
* (Markdown-Extra syle) fenced codeblocks. [From Jason Blevins]
* Fortran syntax colouring. [From Jason Blevins]
Bugs Fixed:
* Fixed some Ruby 1.9 encoding issues, with unicode page names,
author names and categories.
* Better display of inter-web wikilinks
* various syntax-colouring fixes
* Corrected length of wiki_references referenced_name (affects
MySQL users)
------------------------------------------------------------------------------
* 0.18
New Features:
* Syntax colouring: 'ansic', 'javascript', 'sqlite', 'yaml' and 'css' modes,
in addition to the existing 'html', 'xml', and 'ruby' modes,
* Source view [suggested by Andrew Stacey]
* Auto-resizing Textareas scale to fit viewing area.
* Instiki upgraded to Rails 2.3.5 and Rack 1.1.
* Now runs on Ruby 1.9. (If you're a Passenger user, you may need to upgrade to Passenger
2.2.8, which works around some bugs in Ruby 1.9.1.)
* Upgraded for itex2MML 1.3.19 (which works under Ruby 1.9, and has several new feautures,
relative to 1.3.15).
Bugs Fixed:
* Fixed a CSS bug, which screwed up printing (unless you used the "Print" view).
* Fixed a well-formedness bug in the page-name truncation algorithm [reported by Toby Bartels]
* Fixed a cache-sweeping bug [reported by Toby Bartels]
* Better accessibility.
* Improved log rotation under Passenger.
* Omit a (seemingly superfluous) javascript hack which causes Gecko-based browsers to request
/my_wiki/s5/null
when they load an s5 slideshow.
* Upgraded vendored sqlite3-ruby and rubyzip
* Move files when renaming a web (so that links to uploaded files don't break).
* Many Ruby 1.9 fixes, including removing the html5lib Sanitizer.
------------------------------------------------------------------------------
* 0.17.3
The most important facet of this release is a small change in the database
schema. Previously, people migrating from the default SQLite3 database to MySQL
ran the risk of silent data loss, because MySQL had a more strict interpretation
of the column types in the database. The new schema will prevent such problems.
rake upgrade_instiki
will seamlessly upgrade your existing database to the new schema.
New Features:
* Passenger support (including X-Sendfile support, if the Apache mod_xsendfile
module is installed).
* Update for itex2MML 1.3.13. (You should upgrade your itex2MML to the latest
version, too.)
Bugs Fixed:
* Refactored the Web model (from James Herdman).
* Clean malformed utf-8 strings, rather than complaining about them.
* Updated location of Textile help, since _why_the_lucky_stiff left the 'net.
* Fixed a TeX rendering bug.
* Updated list of XHTML+MathML named entities to match W3C Working Draft.
* Refactored the Sanitizer (speedup).
* Fix S5 Slideshows for non-root Instiki URLs.
* Work around a Rails flash bug.
* Links from published webs should work right (finally?).
* An important database migration for MySQL users.
------------------------------------------------------------------------------
* 0.17.2
Security: Updated to Rails 2.3.4
* Fixes Timing Weakness in Rails MessageVerifier and the Cookie Store
http://weblog.rubyonrails.org/2009/9/4/timing-weakness-in-ruby-on-rails
* Fixes XSS Vulnerability in Rails
http://weblog.rubyonrails.org/2009/9/4/xss-vulnerability-in-ruby-on-rails
New Features:
* Syntax colouring (`ruby` and `html`) for code blocks.
* Updated for itex2MML 1.3.10 (supports \rlap{} and \underline{}). You should upgrade that, too.
* Add a "Create New Page" Link to the Search Page. (Based on an idea by nowa)
* Updated to Rails 2.3.4
Bugs Fixed:
* Wikilinks to published webs should be to the published action. This didn't work
right for inter-web links. (Reported by Mike Shulman)
* Use .size, rather than .length for ActiveRecord associations. A huge memory saving
in building the recently_revised page.
* Refactor the upgrade_instiki rake task, to make it database-agnostic. (Many thanks to James Herdman)
* Web#files_path and Web#blatex_pngs_path now return Pathname objects. (Thanks, again, to James Herdman)
* Workaround for Mozilla Bug 449396. (Reported by Andrew Stacey)
* Correctly Set noindex,nofollow On /diff Pages.
* Page-renaming javascript deals correctly with page names containing ampersands, slashes, and other garbage.
* List of Wanted Pages should not include redirected pages.
* The Regexp, used in Maruku to detect "email" headers (used, e.g., for S5 slideshow metadata) could, for some inputs, interact badly with Instiki's Chunk Handler. Fixed.
* Ensure "rollback" locks page for editing.
* Generate relative URLs, when possible. (Patch by Dennis Knauf)
* Expire revisions of an edited page. Use a `before_save` hook to deal with the situation where a page's name has been changed.
------------------------------------------------------------------------------
* 0.17
New features:
* Ability to rename pages
* Ability to redirect Wikilinks, using
[[!redirect ...]]
* HTTP 302 redirects, for redirected/renamed pages
Bugs Fixed:
* Rails gets very unhappy with "." in page or author names.
Make sure that doesn't happen.
* Fix a Maruku escaping bug.
* WEBrick should respond to TERM signals
(needed by MacOSX and, perhaps, others).
* Add a flash message for redirection to "new" page
when the target of "show" action is not found.
* Flash[:info] messages use Web's colour scheme.
* Uploaded files in published webs should be accessible
------------------------------------------------------------------------------
* 0.16.6
New Features:
* More colour schemes: blue, brown, scarlet red, and plum.
(From Jason Blevins)
* History Pages: created a history page for each wiki page.
Link to it and to the "Diff" page from "Recently Revised".
(from Jason Blevins)
* Support for SVG clipping paths
(requested by Andrew Stacey)
* Updated for itex2MML 1.3.8. (You should
upgrade that, as well.) Support for blackboard bold lowercase
letters and digits.
Bugs Fixed:
* Fixed several bugs in Maruku, where "greedy" regexps
could lead to exponential slowdown on certain inputs.
* Fixed a bug in listing/deleting links to uploaded video and
audio files.
* Fixed some caching bugs.
* Removed the defunct list.dsbl.org from anti-spam dnsbl lookups.
* Resolved a conflict between form_spam_protect plugin and IE7.
(thanks to Jason Blevins)
------------------------------------------------------------------------------
* 0.16.5: Rails 2.3.2
* Runs on the Stable Release, Rails 2.3.2.
* Support for audio/speex audio files.
* Updated for itex2MML 1.3.7. (You should
upgrade that, as well.)
* Tests for BlahTeX/PNG (if installed).
------------------------------------------------------------------------------
* 0.16.4
New Features:
* Add x-sendfile support for downloading uploaded files in Instiki.
* Add support for HTML5 <video> and <audio> elements:
[[mymovie.ogv:video]], [[mysound.wav:audio]].
* Runs on Rails 2.3.1
Bugs Fixed:
* Update to Rails 2.3.1, which fixes several bugs.
* Removed bundled Rack (Rails 2.3.1 comes bundled with Rack 1.0).
* Add config.action_view.cache_template_loading = true to production
environment.
* Fix FastCGI bug
(http://rubyforge.org/tracker/index.php?func=detail&aid=24191&group_id=186 &atid=783).
* Fix WikiWords bug
(http://rubyforge.org/pipermail/instiki-users/2009-February/001181.html).
* Fix bug on Windows
(http://rubyforge.org/pipermail/instiki-users/2009-March/001183.html)
------------------------------------------------------------------------------
* 0.16.3: Big steps, Rails 2.3.0-RC1 and tons of new stuff!
All the patching, developing and great hacking from Jacques Distler was
merged into the main codebase of instiki. Jaques is now also maintaining the
main branch (together with parasew), which brings instiki up-to-date!
Find more information about all the changes in his site:
http://golem.ph.utexas.edu/instiki/
New Features:
* Instiki comes and runs with Rails 2.3.0-RC1
* New Markdown interpreter: Maruku
* Default markup dialect is Markdown+itex2MML
* Mathematics suport, using itex2MML (Markdown+itex2MML)
http://golem.ph.utexas.edu/~distler/blog/itex2MMLcommands.html
or BlahTeX (Markdown+BlahTeX/PNG)
http://golem.ph.utexas.edu/instiki/show/BlahTeX
* Theorem Environments
* Automatic Equation numbering and automatic Theorem numbering,
* Integrated presentation software
http://golem.ph.utexas.edu/instiki/show/S5
complete with support for themes
(available with Markdown, Markdown+itex2MML or Markdown+BlahTeX/PNG)
* Sends application/xhtml+xml to compatible browsers
(available with Markdown, Markdown+itex2MML or Markdown+BlahTeX/PNG)
Serve Webs which use non-Markdown Text Filters (Textile, RDoc or Mixed) as
text/html. This makes those Text Filters usable, again.
* Scalable Vector Graphics (SVG) enabled
(available with Markdown, Markdown+itex2MML or Markdown+BlahTeX/PNG).
* Nifty SVG Logo
(available with Markdown, Markdown+itex2MML or Markdown+BlahTeX/PNG).
* Atom 1.0 feeds.
* ETag support.
* Filesystem-based caching.
* New, industrial-strength Sanitizer (anti-XSS protection).
* Uses xhtmldiff for redline diff pages.
* (Actually functional) LaTeX output.
* InterWeb WikiLinks
* Add a user interface to manage uploaded files.
* Add a user interface to delete a Web.
* Add a user interface to delete orphaned pages in a Category. In addition to
deleting all orphaned pages, you can now delete just the orphaned pages in a
particular category. Among other things, this provides a handy way to delete
a (selection of) page(s): just assign them to a new category (“delete”, say)
and delete the orphaned pages in that category.
* Add the manage_fixtures plugin for easy database migration
* Descriptions in the File Upload Dialog are used as the default alt text (for
pictures) or the default link text (for files).
Bugfixes:
(see http://golem.ph.utexas.edu/~distler/blog/archives/001893.html for a
complete list)
* fixes to xhtmldiff
* fixed Sanitizer issues
* Do dnsbl lookups more judiciously. Anti-spam effectiveness is undiminished,
but the application is more responsive.
* Fix a Session CookieOverflow when rescuing Instiki::ValidationError
* Domain independent caching
* Fix for no Flash Messages
* Links on “published” Webs were all screwed-up. Fixed.
* Make uploaded pictures display in the “published” view.
* Make @import rules in the “Stylesheet Tweaks” work in the “published” view.
* Actually verify the password, when setting a password for a Web, rather than
just pretending to do so.
* fixes for nowiki “<nowiki>[[!include foo]]</nowiki>”)
* Hide Equations From WikiChunk Processing
* Fix a bug in the Chunk handler, which was mangling backslashes in included
pages.
* Entering the wrong password on the “CreateWeb” form now redirects back to
the form, as it should.
* Allow single-letter WikiLinks (e.g. “[[a]]”). Requested by a Japanese user.
* Allow single-letter includes (e.g. “[[!include a]]”).
* Huge improvements to caching and cache-sweeping
* Category listing restricts to current Web.
* All WikiReference methods limit results to the current web
* File uploads work right.
* Make WEBrick respond to TERM signal. (Launchd, in particular, requires this.)
* Ditch the URIChunk and LocalURIChunk handlers. Slow, buggy, and of dubious
utility.
* Ensure unsafe operations (new, save,...) are POSTs, not GETs.
* Fix utf-8 bug in WikiChunk handling.
* Disable WikiChunk processing in tags.
* Hide Equations From WikiChunk Processing
* Fix for the "Backslashes in Included Equations" bug.
* Sessions are now stored in a cookie (signed and Base-64 encoded).
Form_spam_protection stores form_keys in the session.
Make sure spambots implement both cookies and javascript, by storing
hashed (with salt) keys in the session.
* Make sure request.ip is a valid IPv4 or IPv6 address.
* Make remove_orphaned_pages work in a proxied situation.
* In the wiki_controller, only apply the dnsbl_check before_filter
to the :edit, :new, and :save :export actions, instead of all actions.
This makes mundane "show" requests faster, but does not
compromise spam-fighting ability.
* Be a little gentler in recovering from Instiki::ValidationErrors, when
saving a page. Previously, we threw away all the user's changes upon
the redirect. Now we attempt to salvage what he wrote.
* Drop hostname from cache key.
* Fix Recursive Includes.
* Entering an incorrect password on the Create Web form should redirect
back to the form, with a flash error.
* In the Stylesheet Tweaks, the owner of a Web can specify an @import rule
to pull in CSS styles form an external file. This worked in the "show"
view, but was broken in the "published" view. Fixed.
* Allow multiple leading capital letters in a WikiWord.
------------------------------------------------------------------------------
* 0.13.0:
Mainly a Bugfix Release, which fixes two XSS Vulnerabilities.
More information can be found on Jacques Distler's Blog:
http://golem.ph.utexas.edu/~distler/blog/archives/001634.html
------------------------------------------------------------------------------
* 0.12.0:
0.12 is mainly a bugfix release. We recommend all instiki Users to upgrade.
In this version, some security holes where fixed
- An XSS vulnerability in categories
- An XSS vulnerability in <nowiki>
- fixes that Instiki allows "dangerous" operations as HTTP GETs
as well as some other small improvements.
- fixes for instiki running on mongrel
- fixes for instiki running on mongrel_cluster
We added a lot of tests, synced with Jacques Distler's version and fixed
small bugs as well. A note to Mac OSX users: use the Ruby One-Click-Installer
for OSX ( http://rubyosx.com ) or make sure you are not running into problems
with sqlite (see http://instiki.5uper.net/instiki/show/SQLite+issues+on+OSX)
------------------------------------------------------------------------------
* 0.11.pl1
- ANTISPAM:
- updated and included spam_patterns.txt
- included dnsbl_check - DNS Blackhole Lists check
[thanks to joost from http://www.spacebabies.nl ]
- included the form-spam-protection rails plugin
http://form-spam-protection.googlecode.com/svn/form_spam_protection/
- BUGFIXES:
- fix PDF output not to contain garbage chars [Jesse Newland]
- fixed the pages and authors display for single webs
- web list does not show a link to a published version if it has none
[Jesse Newland]
- Fixed bug that failed to expire cached diff view of a page
- Fixed rendering of WikiLinks in included pages in published or export
mode
- lots of small bugfixes and changes
- UPDATES:
- Rails 1.2.1 tested and packaged with instiki
- updated RubyZip to 0.9.1
- updated packaged sqlite3-ruby
- FEATURES:
- fix: being logged in on more Webs at once works now [Jaques Distler]
- Stylesheet tweaks
- visual display if webs are pass-protected (div background)
- Linux packaging
------------------------------------------------------------------------------
* 0.11.0:
- SQL-based backend (ActiveRecord)
- File uploads (finally)
- Upgraded to Rails 1.0.0
- Replaced internal link generator with routing
- Fixed --daemon option
- Removed Rubygem and native OS X distributions
- Improved HTML diff
- More accurate "See Changes"
------------------------------------------------------------------------------
* 0.10.2: * 0.10.2:
- Upgraded to Rails 0.13.1 Upgraded to Rails 0.13.1
- Fixed HTML export Fixed HTML export
- Added layout=no option to the export_html action (it exports page Added layout=no option to the export_html action (it exports page contents processed
contents processed by the markup engine, but without the default layout - by the markup engine, but without the default layout - so that they can be wrapped in
so that they can be wrapped in some other layout) some other layout)
- <nowiki> tag can span several lines (before it was applied when both <nowiki> tag can span several lines (before it was applied when both opening and closing
opening and closing tags were on the same line only) tags were on the same line only)
- Resolved the "endless redirection loop" condition and otherwise improved Resolved the "endless redirection loop" condition and otherwise improved handling of
handling of errors in the rendering engines errors in the rendering engines
- Fixed rendering of Markdown hyperlinks such as Fixed rendering of Markdown hyperlinks such as [Text](http://something.com/foo)
[Text](http://something.com/foo)
------------------------------------------------------------------------------
* 0.10.1: * 0.10.1:
- Upgraded Rails to 0.12.0 Upgraded Rails to 0.12.0
- Upgraded rubyzip to version 0.5.8 Upgraded rubyzip to version 0.5.8
- BlueCloth is back (RedCloth didn't do pure Markdown well enough) BlueCloth is back (RedCloth didn't do pure Markdown well enough)
- Handling of line breaks in Textile is as in 0.9 (inserts <br/> tag) Handling of line breaks in Textile is as in 0.9 (inserts <br/> tag)
- Fixed HTML export (to enclose the output in <html> tags, include the Fixed HTML export (to enclose the output in <html> tags, include the stylesheet etc)
stylesheet etc) Corrected some compatibility issues with storages from earlier Instiki versions
- Corrected some compatibility issues with storages from earlier Instiki Some other bug fixes
versions
- Some other bug fixes
------------------------------------------------------------------------------
* 0.10.0: * 0.10.0:
- Ported to ActionPack Ported to ActionPack
- RedCloth 3.0.3 RedCloth 3.0.3
- BlueCloth is phased out, Markdown is rendered by RedCloth BlueCloth is phased out, Markdown is rendered by RedCloth
- Mix markup option understands both Textile and Markdown on the same page Mix markup option understands both Textile and Markdown on the same page
- Instiki can serve static content (such as HTML or plain-text files) from Instiki can serve static content (such as HTML or plain-text files) from ./public
./public directory directory
- Much friendlier admin interface Much friendlier admin interface
- Wiki link syntax doesn't conflict with Textile hyperlink syntax. Wiki link syntax doesn't conflict with Textile hyperlink syntax. Therefore
Therefore "textile link":LinkToSomePlace will not look insane. "textile link":LinkToSomePlace will not look insane.
- RSS feeds accept query parameters, such as RSS feeds accept query parameters, sush as
http://localhost:2500/wiki/rss_with_headlines?start=2005-02-18&end=2005-02-19&limit=10 http://localhost:2500/wiki/rss_with_headlines?start=2005-02-18&end=2005-02-19&limit=10
- RSS feed with page contents for a password-protected web behaves as RSS feed with page contents for a password-protected web behaves as follows:
follows: if the web is published, RSS feed links to the published version if the web is published, RSS feed links to the published version of the web
of the web. otherwise, the feed is not available otherwise, the feed is not available
Madeleine will check every hour if there are new commands in the log or Madeleine will check every hour if there are new commands in the log or 24 hours have
24 hours have passed since last snapshot, and take snapshot if either of passed since last snapshot, and take snapshot if either of these conditions is true
these conditions is true. Madeleine will also not log read-only Madeleine will also not log read-only operations, resulting in a better performance
operations, resulting in a better performance Wiki extracts (to HTML and plain text) will leave only the last extract file in ./storage
- Wiki extracts (to HTML and plain text) will leave only the last extract Wiki search handles multibyte (UTF-8) characters correctly
file in ./storage Local hyperlinks in published pages point to published pages [Michael DeHaan]
- Wiki search handles multibyte (UTF-8) characters correctly Fixed a bug that sometimes caused all past revisions of a page to be "forgotten" on
- Local hyperlinks in published pages point to published pages [Michael restart
DeHaan] Fixed parsing of URIs with a port number (http://someplace.org:8080)
- Fixed a bug that sometimes caused all past revisions of a page to be Instiki will not fork itself on a *nix, unless explicitly asked to
"forgotten" on restart Instiki can bind to IPs other than 127.0.0.1 (command-line option)
- Fixed parsing of URIs with a port number (http://someplace.org:8080) Revisions that do not change anything on the page are rejected
- Instiki will not fork itself on a *nix, unless explicitly asked to Automated tests for all controller actions
- Instiki can bind to IPs other than 127.0.0.1 (command-line option) category: lines are presented as links to "All Pages" for relevant categories
- Revisions that do not change anything on the page are rejected Search looks at page titles, as well as content
- Automated tests for all controller actions Multiple other usability enhancements and bug fixes
- category: lines are presented as links to "All Pages" for relevant
categories
- Search looks at page titles, as well as content
- Multiple other usability enhancements and bug fixes
------------------------------------------------------------------------------
* 0.9.2: * 0.9.2:
- Rollback takes the user to an edit form. The form has to be submitted for Rollback takes the user to an edit form. The form has to be submitted for the change to
the change to take place. take place.
- Changed to use inline style on published pages Changed to use inline style on published pages
- Fixed "forward in time" on the last revision before current page Fixed "forward in time" on the last revision before current page
- Instiki won't log bogus error messages when creating a new Wiki Instiki won't log bogus error messages when creating a new Wiki
- Fixed deprecation warning for Object.id (introduced in Ruby 1.8.2) Fixed deprecation warning for Object.id (introduced in Ruby 1.8.2)
- Madeleine upgraded to 0.7.1 Madeleine upgraded to 0.7.1
- Madeleine snapshots are compressed Madeleine snapshots are compressed
- Packaged as a gem Packaged as a gem
------------------------------------------------------------------------------
* 0.9.1: * 0.9.1:
- Added performance improvements for updating existing pages Added performance improvements for updating existing pages
- Fixed IP logging and RSS feeds behind proxies [With help from Guan Yang] Fixed IP logging and RSS feeds behind proxies [With help from Guan Yang]
- Fixed default storage directory (borked running on Windows) Fixed default storage directory (borked running on Windows) [Spotted by Curt Hibbs]
[Spotted by Curt Hibbs]
------------------------------------------------------------------------------
* 0.9.0: * 0.9.0:
- Added aliased links such as [[HomePage|that nice home page]] [Mark Reid] Added aliased links such as [[HomePage|that nice home page]] [Mark Reid]
- Added include other page content with [[!include TableOfContents]] Added include other page content with [[!include TableOfContents]] [Mark Reid]
[Mark Reid] Added delete orphan pages from the Edit Web screen [by inspiration from Simon Arnaud]
- Added delete orphan pages from the Edit Web screen [by inspiration from Added logging of IP address for authors (who's behind the rollback wars)
Simon Arnaud] Added Categories pages through backlinks (use "categories: news, instiki" on start of line) [Mark Reid]
- Added logging of IP address for authors (who's behind the rollback wars) Added option to use bracket-style wiki links only (and hence ban WikiWords)
- Added Categories pages through backlinks (use "categories: news, instiki" Added command-line option to specify different storage path
on start of line) [Mark Reid] Added print view without navigation
- Added option to use bracket-style wiki links only (and hence ban Added character and page (2275 characters including spaces) counter (important for student papers)
WikiWords) Off by default, activate it on the Edit Web screen
- Added command-line option to specify different storage path Added LaTeX/PDF integration on Textile installations with pdflatex installed on system (EXPERIMENTAL)
- Added print view without navigation Use the home page as a table of contents with a unordered list to control sections
- Added character and page (2275 characters including spaces) counter Added limit of 15 to the number of pages included in RSS feed
(important for student papers) Moved static parts of stylesheet to separate file [Lau TŒrnskov]
- Off by default, activate it on the Edit Web screen Fixed better semantics for revision movement [Ryan Singer]
- Added LaTeX/PDF integration on Textile installations with pdflatex Fixed color diffs to work much better [Xen/Mertz/Atkins]
installed on system (EXPERIMENTAL) Fixed performance problems for All Pages list [Dennis Mertz]
- Use the home page as a table of contents with a unordered list to control Fixed lots of rendering bugs [Mark Reid]
sections Upgraded to RedCloth 2.0.11 [integrating the fine work of Dennis Mertz]
- Added limit of 15 to the number of pages included in RSS feed
- Moved static parts of stylesheet to separate file [Lau T?rnskov]
- Fixed better semantics for revision movement [Ryan Singer]
- Fixed color diffs to work much better [Xen/Mertz/Atkins]
- Fixed performance problems for All Pages list [Dennis Mertz]
- Fixed lots of rendering bugs [Mark Reid]
- Upgraded to RedCloth 2.0.11 [integrating the fine work of Dennis Mertz]
------------------------------------------------------------------------------
* 0.8.9: * 0.8.9:
- Added color diffs to see changes between revisions [Bill Atkins] Added color diffs to see changes between revisions [Bill Atkins]
They're aren't quite perfect yet as new paragraphs split the <ins> tags They're aren't quite perfect yet as new paragraphs split the <ins> tags (hence 0.8.9, not 0.9.0)
(hence 0.8.9, not 0.9.0) Added redirect to edit if content of page generates an error
- Added redirect to edit if content of page generates an error
(so the page doesn't become unusable on bugs in the markup engines) (so the page doesn't become unusable on bugs in the markup engines)
- Fixed update Web with different address bug [Denis Metz] Fixed update Web with different address bug [Denis Metz]
- Fixed a bunch of wiki word rendering issues by doing wiki word detection Fixed a bunch of wiki word rendering issues by doing wiki word detection and replacment at once
and replacment at once Upgraded to BlueCloth 0.0.3b (should fix loads of problems on Markdown wikis)
- Upgraded to BlueCloth 0.0.3b (should fix loads of problems on Markdown
wikis)
------------------------------------------------------------------------------
* 0.8.5: * 0.8.5:
- Instiki can now serve as a CMS by running a password-protected web with a Instiki can now serve as a CMS by running a password-protected web with a published front
published front Added version check at startup (Instiki needs Ruby 1.8.1)
- Added version check at startup (Instiki needs Ruby 1.8.1)
------------------------------------------------------------------------------
* 0.8.1: * 0.8.1:
- Actually included RedCloth 2.0.7 in the release Actually included RedCloth 2.0.7 in the release
------------------------------------------------------------------------------
* 0.8.0: * 0.8.0:
- NOTE: Single-web wikis created in versions prior to 0.8.0 have "instiki" NOTE: Single-web wikis created in versions prior to 0.8.0 have "instiki" as their system password
as their system password Accepts wiki words in bracket style. Ex: [[wiki word]], [[c]], [[We could'nt have done it!]]
- Accepts wiki words in bracket style. Accepts camel-case wiki words in all latin, greek, cyrillian, and armenian unicode characters
Examples: [[wiki word]], [[c]], [[We could'nt have done it!]] Many thanks to Guan Yang for building the higher- and lower-case lookup tables
- Accepts camel-case wiki words in all latin, greek, cyrillian, and And thanks to Simon Arnaud for the initial patch that got the work started
armenian unicode characters Changed charset to UTF-8
- Many thanks to Guan Yang for building the higher- and lower-case lookup Cut down on command-line options and replaced them with an per-web config screen
tables. And thanks to Simon Arnaud for the initial patch that got the Added option to extend the stylesheet on a per-web basis to tweak the look in details
work started Added simple color options for variety
- Changed charset to UTF-8 Added option to add/remove password protection on each web
- Cut down on command-line options and replaced them with an per-web config Added the wiki name of the author locking a given page (instead of just "someone")
screen Removed single/multi-web distinction -- all Instikis are now multi-web
- Added option to extend the stylesheet on a per-web basis to tweak the Load libraries from an unshifted load path, so that old installed libraries doesn't clash [Emiel van de Laar]
look in details Keeps the author cookie forever, so you don't have to enter your name again and again
- Added simple color options for variety Fixed XHTML so it validates [Bruce D'Arcus]
- Added option to add/remove password protection on each web Authors are no longer listed under orphan pages
- Added the wiki name of the author locking a given page (instead of just Added export to markup (great for backups, potentially for switching wiki engine)
"someone") Don't link wiki words that proceeds from either /, = or ?
- Removed single/multi-web distinction -- all Instikis are now multi-web (http://c2.com/cgi/wiki?WikiWikiClones, /show/HomePage, cgi.pl?show=WikiWord without escaping)
- Load libraries from an unshifted load path, so that old installed Accessing an unexisting page redirects to a different url (/new/PageName)
libraries doesn't clash [Emiel van de Laar] Increased snapshot time to just once a day (cuts down on disk storage requirements)
- Keeps the author cookie forever, so you don't have to enter your name Made RDoc support work better with 1.8.1 [Mauricio Fern‡ndez]
again and again Added convinient redirect from /wiki/ to /wiki/show/HomePage
- Fixed XHTML so it validates [Bruce D'Arcus] Fixed BlueCloth bug with backticks at start of line
- Authors are no longer listed under orphan pages Updated to RedCloth 2.0.7 (and linked to the new Textile reference)
- Added export to markup (great for backups, potentially for switching wiki
engine)
- Don't link wiki words that proceeds from either /, = or ?
(http://c2.com/cgi/wiki?WikiWikiClones,
/show/HomePage, cgi.pl?show=WikiWord without escaping)
- Accessing an unexisting page redirects to a different url (/new/PageName)
- Increased snapshot time to just once a day (cuts down on disk storage
requirements)
- Made RDoc support work better with 1.8.1 [Mauricio Fern?ndez]
- Added convinient redirect from /wiki/ to /wiki/show/HomePage
- Fixed BlueCloth bug with backticks at start of line
- Updated to RedCloth 2.0.7 (and linked to the new Textile reference)
------------------------------------------------------------------------------
* 0.7.0: * 0.7.0:
- Added Markdown (BlueCloth) and RDoc [Mauricio Fern?ndez] as command-line Added Markdown (BlueCloth) and RDoc [Mauricio Fern‡ndez] as command-line markup choices
markup choices Added wanted and orphan page lists to All pages (only show up if there's actually orphan or wanted pages)
- Added wanted and orphan page lists to All pages (only show up if there's Added ISO-8859-1 as XML encoding in RSS feeds (makes FeedReader among others happy for special entities)
actually orphan or wanted pages) Added proper links in the RSS feed (but the body links are still relative, which NNW and others doesn't grok)
- Added ISO-8859-1 as XML encoding in RSS feeds (makes FeedReader among Added access keys: E => Edit, H => HomePage, A => All Pages, U => Recently Revised, X => Export
others happy for special entities) Added password-login through URL (so you can subscribe to feed on a protected web)
- Added proper links in the RSS feed (but the body links are still Added web passwords to the feed links for protected webs, so they work without manual login
relative, which NNW and others doesn't grok) Added the web name in small letters above all pages within a web
- Added access keys: E => Edit, H => HomePage, A => All Pages, Polished authors and recently revised
U => Recently Revised, X => Export Updated to RedCloth 2.0.6
- Added password-login through URL (so you can subscribe to feed on a Changed content type for RSS feeds to text/xml (makes Mozilla Aggreg8 happy)
protected web) Changed searching to be case insensitive
- Added web passwords to the feed links for protected webs, so they work Changed HomePage to display the name of the web instead
without manual login Changed exported HTML pages to be valid XHTML (which can be preprocessed by XSLT)
- Added the web name in small letters above all pages within a web Fixed broken recently revised
- Polished authors and recently revised
- Updated to RedCloth 2.0.6
- Changed content type for RSS feeds to text/xml (makes Mozilla Aggreg8
happy)
- Changed searching to be case insensitive
- Changed HomePage to display the name of the web instead
- Changed exported HTML pages to be valid XHTML (which can be preprocessed
by XSLT)
- Fixed broken recently revised
------------------------------------------------------------------------------
* 0.6.0: * 0.6.0:
- Fixed Windows compatibility [Florian] Fixed Windows compatibility [Florian]
- Fixed bug that would prevent Madeleine from taking snapshots in Daemon Fixed bug that would prevent Madeleine from taking snapshots in Daemon mode
mode Added export entire web as HTML in a zip file
- Added export entire web as HTML in a zip file Added RSS feeds
- Added RSS feeds Added proper getops support for the growing number of options [Florian]
- Added proper getops support for the growing number of options [Florian] Added safe mode that forbids style options in RedCloth [Florian]
- Added safe mode that forbids style options in RedCloth [Florian] Updated RedCloth to 2.0.5
- Updated RedCloth to 2.0.5
------------------------------------------------------------------------------
* 0.5.0: * 0.5.0:
- NOTE: 0.5.0 is NOT compatible with databases from earlier versions NOTE: 0.5.0 is NOT compatible with databases from earlier versions
- Added revisions Added revisions
- Added multiple webs Added multiple webs
- Added password protection for webs on multi-web setups Added password protection for webs on multi-web setups
- Added the notion of authors (that are saved in a cookie) Added the notion of authors (that are saved in a cookie)
- Added command-line option for not running as a Daemon on Unix Added command-line option for not running as a Daemon on Unix
------------------------------------------------------------------------------
* 0.3.1: * 0.3.1:
- Added option to escape wiki words with \ Added option to escape wiki words with \
------------------------------------------------------------------------------
* 0.3.0: * 0.3.0:
- Brought all files into common style (including Textile help on the edit Brought all files into common style (including Textile help on the edit page)
page) Added page locking (if someone already is editing a page there's a warning)
- Added page locking (if someone already is editing a page there's a Added daemon abilities on Unix (keep Instiki running after you close the terminal)
warning) Made port 2500 the default port, so Instiki can be launched by dobbelt-click
- Added daemon abilities on Unix (keep Instiki running after you close the Added Textile cache to speed-up rendering of large pages
terminal) Made WikiWords look like "Wiki Words"
- Made port 2500 the default port, so Instiki can be launched by Updated RedCloth to 2.0.4
dobbelt-click
- Added Textile cache to speed-up rendering of large pages
- Made WikiWords look like "Wiki Words"
- Updated RedCloth to 2.0.4
------------------------------------------------------------------------------
* 0.2.5: * 0.2.5:
- Upgraded to RedCloth 2.0.2 and Madeleine 0.6.1, which means the Upgraded to RedCloth 2.0.2 and Madeleine 0.6.1, which means the
- Windows problems are gone. Also fixed a problem with wikiwords Windows problems are gone. Also fixed a problem with wikiwords
- that used part of other wikiwords. that used part of other wikiwords.
------------------------------------------------------------------------------
* 0.2.0: * 0.2.0:
- First public release First public release

12
Gemfile
View file

@ -1,12 +0,0 @@
source "http://rubygems.org"
gem "sqlite3-ruby", :require => "sqlite3"
gem "itextomml", ">=1.4.7"
gem "rack", ">=1.1.0"
gem "mongrel", ">=1.2.0.pre2"
gem "rubyzip"
gem "RedCloth", ">=4.0.0"
gem "erubis"
gem "nokogiri"
gem "rake"
gem "rdoc"
gem "json"

140
README Normal file → Executable file
View file

@ -1,66 +1,24 @@
===What is Instiki?
= Instiki Admitted, it's YetAnotherWikiClone[http://c2.com/cgi/wiki?WikiWikiClones], but with a strong focus
on simplicity of installation and running:
Instiki is a wiki clone so pretty and easy to set up, you'll wonder if its really a wiki. Runs on Rails and focuses on portability and stability. Supports file uploads, PDF export, RSS, multiple users and password protection. Some use Instiki as a CMS (Content Management System) because of its ability to export static pages. Step 1. Download
Instiki lowers the barriers of interest for when you might consider using a wiki. It's so simple to get running that you'll find yourself using it for anything -- taking notes, brainstorming, organizing a gathering.
== Supported Platforms
Instiki only relies on Ruby - the sole external dependency (it includes all other dependencies). Any OS that can run Ruby can run Instiki - that includes Windows, Linux, Mac OS X and most known Unix flavors.
Instiki on BeOS, Amiga OS, OS2, Zeta OS and support for various exotic Platforms is planned. Mostly it already works, if Ruby runs there (download the linux version in this case). Please contact parasew if you want to help out for your OS that is not listed. Please also get in touch if you are able to create an instiki package for your favorite unix or linux distribution.
== 3 easy Steps to get the Instiki experience
Step 1. Get Instiki and run "ruby bundle"
Step 2. Run "instiki" Step 2. Run "instiki"
Step 3. Chuckle... "There's no step three!" (TM) Step 3. Chuckle... "There's no step three!" (TM)
You're now running a perfectly suitable wiki on port 2500
that'll present you with one-step setup, followed by a textarea for the home page
on http://localhost:2500.
== Details Instiki lowers the barriers of interest for when you might consider
using a wiki. It's so simple to get running that you'll find yourself
You need at least Ruby Version 1.8.6, and Rubygems 1.3.6, installed on your System. The second dependency is a Database System, but don't worry, the default sqlite3 will be installed for you, if it's not already installed. You can also use any other database system (MySQL, PostgreSQL, ...) supported by Rails. using it for anything -- taking notes, brainstorming, organizing a
gathering.
=== If you are on Windows
- Get the *Ruby One-Click Installer - Windows* http://rubyforge.org/projects/rubyinstaller
- Get Development Kit http://github.com/oneclick/rubyinstaller/wiki/development-kit
- In the Instiki directory, execute "ruby bundle"
- double-click instiki.bat or instiki.cmd and there you go!
=== If you are on Mac OSX
On Leopard and Snow Leopard, you are all set.
- run "sudo gem update --system" via the command-line.
- run "ruby bundle" in the instiki directory.
- run "ruby instiki" and there you go!
Tiger ships with a really old Ruby Version (1.8.2) and a broken Readline Library you have to
- use the Ruby One-Click-Installer for OSX ( http://rubyosx.com ) if you don't already have macports' Ruby
=== If you are on Linux
=== Any other System
- get Ruby for your System, compile if nessesary: http://ruby-lang.org
- Depending on the version of Rubygems that came with your Ruby, you may need to
sudo gem update --system
- get SQLite or compile from http://sqlite.org (you can also use mysql or any other supported database system if you want)
- run "ruby bundle"
- run instiki
You're now running a perfectly suitable wiki on port 2500 that'll present you with one-step setup, followed by a textarea for the home page on http://localhost:2500
== Features
===Features:
* Regular expression search: Find deep stuff really fast * Regular expression search: Find deep stuff really fast
* Revisions: Follow the changes on every page from birth. Rollback to an earlier rev * Revisions: Follow the changes on every page from birth. Rollback to an earlier rev
* Export to HTML or markup in a zip: Take the entire wiki with you home or for reference * Export to HTML or markup in a zip: Take the entire wiki with you home or for reference
@ -69,66 +27,42 @@ You're now running a perfectly suitable wiki on port 2500 that'll present you wi
* Password-protected webs: Keep it private * Password-protected webs: Keep it private
* Authors: Each revision is associated with an author, so you can see who changed what * Authors: Each revision is associated with an author, so you can see who changed what
* Reference tracker: Which other pages are pointing to the current? * Reference tracker: Which other pages are pointing to the current?
* Five markup choices: * Speed: Using Madelein[http://madeleine.sourceforge.net] for persistence (all pages are in memory)
Markdown-based choices [http://daringfireball.net/projects/markdown/syntax]: * Three markup choices: Textile[http://www.textism.com/tools/textile]
Markdown+itex2MML (the default; requires itex2MML) (default / RedCloth[http://www.whytheluckystiff.net/ruby/redcloth]),
Markdown+BlahTeX/PNG (requires blahtex and a working TeX installation) Markdown (BlueCloth[http://bluecloth.rubyforge.org]), and RDoc[http://rdoc.sourceforge.net/doc]
Markdown * Embedded webserver: Through WEBrick[http://www.webrick.org]
Textile [http://www.textism.com/tools/textile]
RDoc [http://rdoc.sourceforge.net/doc]
* Support for Math (using itex syntax [http://golem.ph.utexas.edu/~distler/blog/itex2MMLcommands.html])
* Support for WYSIWYG SVG editing -- embed SVG graphics right in your wiki page.
* Embedded webserver: uses Mongrel (if installed), or the bundled WEBrick webserver (if not).
* Internationalization: Wiki words in any latin, greek, cyrillian, or armenian characters * Internationalization: Wiki words in any latin, greek, cyrillian, or armenian characters
* Color diffs: Track changes through revisions * Color diffs: Track changes through revisions
* Runs on SQLite3 per default, can be configured to run on PostgreSQL, MySQL, DB2, Firebird, Openbase, Oracle, SQL Server or Sybase
===Missing:
* File attachments
== Command-line options: ===Install from gem:
* Install rubygems
* Run "gem install instiki"
* Change to a directory where you want Instiki to keep its data files (for example, ~/instiki/)
* Run "instiki" - this will create a "storage" directory (for example, ~/instiki/storage), and start a new Wiki service
* Run "ruby instiki --help" Make sure that you always launch Instiki from the same working directory, or specify the storage directory in runtime parameters, such as:
instiki --storage ~/instiki/storage
===Command-line options:
* Run "instiki --help"
== History: ===History:
* See CHANGELOG * See CHANGELOG
== Migrating from Instiki 0.11-0.18 to 0.19 ===Download latest from:
ruby bundle
ruby bundle exec rake upgrade_instiki
== Download the latest release from:
* http://rubyforge.org/project/showfiles.php?group_id=186 * http://rubyforge.org/project/showfiles.php?group_id=186
===Visit the official Instiki wiki:
* http://www.instiki.org
== Visit the Instiki wiki: ===License:
* http://golem.ph.utexas.edu/wiki/instiki/
== License:
* same as Ruby's * same as Ruby's
--- ---
Author:: David Heinemeier Hansson
Authors:: Email:: david@loudthinking.com
Weblog:: http://www.loudthinking.com
Versions 0.0 to 0.9.1:: David Heinemeier Hansson
Email:: david[AT]loudthinking.com
Weblog:: http://www.loudthinking.com[http://www.loudthinking.com]
From 0.9.2 onwards:: Alexey Verkhovsky
Email:: alex[AT]verk.info
From 0.11 onwards:: Matthias Tarasiewicz and 5uper.net
Email:: parasew[AT]gmail.com
Website:: http://5uper.net[http://5uper.net]
From 0.13 onwards:: Matthias Tarasiewicz and Jacques Distler
Email:: instiki-devel[AT]rubyforge.org
Weblog Jacques: http://golem.ph.utexas.edu/~distler/blog/
Weblog Parasew: http://parasew.com

View file

@ -1,21 +0,0 @@
UPGRADING
=========
See the upgrading instructions
http://golem.ph.utexas.edu/wiki/instiki/show/Upgrading
for detailed instructions.
At a minimum, you need to backup your database.
After installing the new software and restoring your database, you need to run
ruby bundle
ruby bundle exec rake upgrade_instiki
from the commandline, to complete the upgrade. Doing a
ruby bundle update
will update the installed gems (used by Instiki) to the latest versions.

0
app/apis/.gitignore vendored
View file

View file

@ -1,8 +1,8 @@
require 'application'
class AdminController < ApplicationController class AdminController < ApplicationController
layout 'default' layout 'default'
cache_sweeper :web_sweeper
before_filter :dnsbl_check
def create_system def create_system
if @wiki.setup? if @wiki.setup?
@ -12,12 +12,12 @@ class AdminController < ApplicationController
"\n\n" + "\n\n" +
"(WARNING: this will destroy content of your current wiki)." "(WARNING: this will destroy content of your current wiki)."
redirect_home(@wiki.webs.keys.first) redirect_home(@wiki.webs.keys.first)
elsif params['web_name'] elsif @params['web_name']
# form submitted -> create a wiki # form submitted -> create a wiki
@wiki.setup(params['password'], params['web_name'], params['web_address']) @wiki.setup(@params['password'], @params['web_name'], @params['web_address'])
flash[:info] = "Your new wiki '#{params['web_name']}' is created!\n" + flash[:info] = "Your new wiki '#{@params['web_name']}' is created!\n" +
"Please edit its home page and press Submit when finished." "Please edit its home page and press Submit when finished."
redirect_to :web => params['web_address'], :controller => 'wiki', :action => 'new', redirect_to :web => @params['web_address'], :controller => 'wiki', :action => 'new',
:id => 'HomePage' :id => 'HomePage'
else else
# no form submitted -> go to template # no form submitted -> go to template
@ -25,22 +25,20 @@ class AdminController < ApplicationController
end end
def create_web def create_web
if params['address'] if @params['address']
return unless is_post
# form submitted # form submitted
if @wiki.authenticate(params['system_password']) if @wiki.authenticate(@params['system_password'])
begin begin
@wiki.create_web(params['name'], params['address']) @wiki.create_web(@params['name'], @params['address'])
flash[:info] = "New web '#{params['name']}' successfully created." flash[:info] = "New web '#{@params['name']}' successfully created."
redirect_to :web => params['address'], :controller => 'wiki', :action => 'new', redirect_to :web => @params['address'], :controller => 'wiki', :action => 'new',
:id => 'HomePage' :id => 'HomePage'
rescue Instiki::ValidationError => e rescue Instiki::ValidationError => e
@error = e.message @error = e.message
# and re-render the form again # and re-render the form again
end end
else else
flash[:error] = "System Password incorrect. Try again." redirect_to :controller => 'wiki', :action => 'index'
redirect_to :controller => 'admin', :action => 'create_web'
end end
else else
# no form submitted -> render template # no form submitted -> render template
@ -48,30 +46,27 @@ class AdminController < ApplicationController
end end
def edit_web def edit_web
system_password = params['system_password']
system_password = @params['system_password']
if system_password if system_password
return unless is_post
# form submitted # form submitted
if wiki.authenticate(system_password) if wiki.authenticate(system_password)
begin begin
raise Instiki::ValidationError.new("Password for this Web didn't match") unless
(params['password'].empty? or params['password'] == params['password_check'])
wiki.edit_web( wiki.edit_web(
@web.address, params['address'], params['name'], @web.address, @params['address'], @params['name'],
params['markup'].intern, @params['markup'].intern,
params['color'], params['additional_style'], @params['color'], @params['additional_style'],
params['safe_mode'] ? true : false, @params['safe_mode'] ? true : false,
params['password'].empty? ? nil : params['password'], @params['password'].empty? ? nil : @params['password'],
params['published'] ? true : false, @params['published'] ? true : false,
params['brackets_only'] ? true : false, @params['brackets_only'] ? true : false,
params['count_pages'] ? true : false, @params['count_pages'] ? true : false,
params['allow_uploads'] ? true : false, @params['allow_uploads'] ? true : false,
params['max_upload_size'] @params['max_upload_size']
) )
flash[:info] = "Web '#{params['address']}' was successfully updated" flash[:info] = "Web '#{@params['address']}' was successfully updated"
redirect_home(params['address']) redirect_home(@params['address'])
rescue Instiki::ValidationError => e rescue Instiki::ValidationError => e
logger.warn e.message
@error = e.message @error = e.message
# and re-render the same template again # and re-render the same template again
end end
@ -85,57 +80,14 @@ class AdminController < ApplicationController
end end
def remove_orphaned_pages def remove_orphaned_pages
return unless is_post if wiki.authenticate(@params['system_password_orphaned'])
if wiki.authenticate(params['system_password_orphaned'])
wiki.remove_orphaned_pages(@web_name) wiki.remove_orphaned_pages(@web_name)
flash[:info] = 'Orphaned pages removed' flash[:info] = 'Orphaned pages removed'
redirect_to :controller => 'wiki', :web => @web_name, :action => 'list' redirect_to :controller => 'wiki', :web => @web_name, :action => 'list'
else else
flash[:error] = password_error(params['system_password_orphaned']) flash[:error] = password_error(@params['system_password_orphaned'])
redirect_to :controller => 'admin', :web => @web_name, :action => 'edit_web' redirect_to :controller => 'admin', :web => @web_name, :action => 'edit_web'
end end
end end
def remove_orphaned_pages_in_category
return unless is_post
if wiki.authenticate(params['system_password_orphaned_in_category'])
category = params['category']
wiki.remove_orphaned_pages_in_category(@web_name, category)
flash[:info] = "Orphaned pages in category \"#{category}\" removed"
redirect_to :controller => 'wiki', :web => @web_name, :action => 'list'
else
flash[:error] = password_error(params['system_password_orphaned_in_category'])
redirect_to :controller => 'admin', :web => @web_name, :action => 'edit_web'
end
end
def delete_web
return unless is_post
if wiki.authenticate(params['system_password_delete_web'])
wiki.delete_web(@web_name)
flash[:info] = "Web \"#{@web_name}\" has been deleted."
redirect_to :controller => 'wiki', :action => 'web_list'
else
flash[:error] = password_error(params['system_password_delete_web'])
redirect_to :controller => 'admin', :web => @web_name, :action => 'edit_web'
end
end
def delete_files
return unless is_post
some_deleted = false
if wiki.authenticate(params['system_password'])
params.each do |file, p|
if p == 'delete'
WikiFile.find_by_file_name(file).destroy
some_deleted = true
end
end
flash[:info] = "File(s) successfully deleted." if some_deleted
else
flash[:error] = password_error(params['system_password'])
end
redirect_to :back
end
end end

View file

@ -0,0 +1,161 @@
# The filters added to this controller will be run for all controllers in the application.
# Likewise will all the methods added be available for all controllers.
class ApplicationController < ActionController::Base
before_filter :set_utf8_http_header, :connect_to_model, :check_snapshot_thread
after_filter :remember_location
# For injecting a different wiki model implementation. Intended for use in tests
def self.wiki=(the_wiki)
# a global variable is used here because Rails reloads controller and model classes in the
# development environment; therefore, storing it as a class variable does not work
# class variable is, anyway, not much different from a global variable
$instiki_wiki_service = the_wiki
logger.debug("Wiki service: #{the_wiki.to_s}")
end
def self.wiki
$instiki_wiki_service
end
protected
def authorized?
@web.nil? ||
@web.password.nil? ||
cookies['web_address'] == @web.password ||
password_check(@params['password'])
end
def check_authorization
if in_a_web? and needs_authorization?(@action_name) and not authorized? and
redirect_to :controller => 'wiki', :action => 'login', :web => @web_name
return false
end
end
def check_snapshot_thread
WikiService.check_snapshot_thread
end
def connect_to_model
@action_name = @params['action'] || 'index'
@web_name = @params['web']
@wiki = wiki
if @web_name
@web = @wiki.webs[@web_name]
if @web.nil?
render_text "Unknown web '#{@web_name}'", '404 Not Found'
return false
end
end
@page_name = @file_name = @params['id']
@page = @wiki.read_page(@web_name, @page_name) unless @page_name.nil?
@author = cookies['author'] || 'AnonymousCoward'
check_authorization
end
FILE_TYPES = {
'.exe' => 'application/octet-stream',
'.gif' => 'image/gif',
'.jpg' => 'image/jpeg',
'.pdf' => 'application/pdf',
'.png' => 'image/png',
'.txt' => 'text/plain',
'.zip' => 'application/zip'
} unless defined? FILE_TYPES
def send_file(file, options = {})
options[:type] ||= (FILE_TYPES[File.extname(file)] || 'application/octet-stream')
options[:stream] = false
super(file, options)
end
def in_a_web?
not @web_name.nil?
end
def password_check(password)
if password == @web.password
cookies['web_address'] = password
true
else
false
end
end
def password_error(password)
if password.nil? or password.empty?
'Please enter the password.'
else
'You entered a wrong password. Please enter the right one.'
end
end
def redirect_home(web = @web_name)
if web
redirect_to_page('HomePage', web)
else
redirect_to_url '/'
end
end
def redirect_to_page(page_name = @page_name, web = @web_name)
redirect_to :web => web, :controller => 'wiki', :action => 'show',
:id => (page_name || 'HomePage')
end
@@REMEMBER_NOT = ['locked', 'save', 'back', 'file', 'pic', 'import']
def remember_location
if @response.headers['Status'] == '200 OK'
unless @@REMEMBER_NOT.include? action_name or @request.method != :get
@session[:return_to] = @request.request_uri
logger.debug("Session ##{session.object_id}: remembered URL '#{@session[:return_to]}'")
end
end
end
def rescue_action_in_public(exception)
message = <<-EOL
<html><body>
<h2>Internal Error 500</h2>
<p>An application error occurred while processing your request.</p>
<!-- \n#{exception}\n#{exception.backtrace.join("\n")}\n -->
</body></html>
EOL
render_text message, 'Internal Error 500'
end
def return_to_last_remembered
# Forget the redirect location
redirect_target, @session[:return_to] = @session[:return_to], nil
tried_home, @session[:tried_home] = @session[:tried_home], false
# then try to redirect to it
if redirect_target.nil?
if tried_home
raise 'Application could not render the index page'
else
logger.debug("Session ##{session.object_id}: no remembered redirect location, trying home")
redirect_home
end
else
logger.debug("Session ##{session.object_id}: " +
"redirect to the last remembered URL #{redirect_target}")
redirect_to_url(redirect_target)
end
end
def set_utf8_http_header
@response.headers['Content-Type'] = 'text/html; charset=UTF-8'
end
def wiki
$instiki_wiki_service
end
def needs_authorization?(action)
not %w( login authenticate published rss_with_content rss_with_headlines ).include?(action)
end
end

View file

@ -1,308 +0,0 @@
# The filters added to this controller will be run for all controllers in the application.
# Likewise will all the methods added be available for all controllers.
class ApplicationController < ActionController::Base
protect_forms_from_spam
before_filter :connect_to_model, :check_authorization, :setup_url_generator, :set_content_type_header, :set_robots_metatag
after_filter :remember_location, :teardown_url_generator
# For injecting a different wiki model implementation. Intended for use in tests
def self.wiki=(the_wiki)
# a global variable is used here because Rails reloads controller and model classes in the
# development environment; therefore, storing it as a class variable does not work
# class variable is, anyway, not much different from a global variable
#$instiki_wiki_service = the_wiki
logger.debug("Wiki service: #{the_wiki.to_s}")
end
def self.wiki
Wiki.new
end
helper_method :xhtml_enabled?, :html_ext, :darken
protected
def xhtml_enabled?
in_a_web? and [:markdownMML, :markdownPNG, :markdown].include?(@web.markup)
end
def html_ext
if xhtml_enabled? && request.env.include?('HTTP_ACCEPT') &&
Mime::Type.parse(request.env["HTTP_ACCEPT"]).include?(Mime::XHTML)
'xhtml'
else
'html'
end
end
def darken(s)
n=s.length/3
s.scan( %r(\w{#{n},#{n}}) ).collect {|a| (a.hex * 2/3).to_s(16).rjust(n,'0')}.join
end
def check_authorization
redirect_to(:controller => 'wiki', :action => 'login',
:web => @web_name) if in_a_web? and authorization_needed? and not authorized?
end
def connect_to_model
@action_name = params['action'] || 'index'
@web_name = params['web']
@wiki = wiki
@author = cookies['author'] || 'AnonymousCoward'
if @web_name
@web = @wiki.webs[@web_name]
render(:status => 404, :text => "Unknown web '#{@web_name}'",
:layout => 'error') if @web.nil?
end
end
FILE_TYPES = {
'.aif' => 'audio/x-aiff',
'.aiff'=> 'audio/x-aiff',
'.avi' => 'video/x-msvideo',
'.exe' => 'application/octet-stream',
'.gif' => 'image/gif',
'.jpg' => 'image/jpeg',
'.pdf' => 'application/pdf',
'.png' => 'image/png',
'.oga' => 'audio/ogg',
'.ogg' => 'audio/ogg',
'.ogv' => 'video/ogg',
'.mov' => 'video/quicktime',
'.mp3' => 'audio/mpeg',
'.mp4' => 'video/mp4',
'.spx' => 'audio/speex',
'.txt' => 'text/plain',
'.tex' => 'text/plain',
'.wav' => 'audio/x-wav',
'.zip' => 'application/zip'
} unless defined? FILE_TYPES
DISPOSITION = {
'application/octet-stream' => 'attachment',
'application/pdf' => 'inline',
'image/gif' => 'inline',
'image/jpeg' => 'inline',
'image/png' => 'inline',
'audio/mpeg' => 'inline',
'audio/x-wav' => 'inline',
'audio/x-aiff' => 'inline',
'audio/speex' => 'inline',
'audio/ogg' => 'inline',
'video/ogg' => 'inline',
'video/mp4' => 'inline',
'video/quicktime' => 'inline',
'video/x-msvideo' => 'inline',
'text/plain' => 'inline',
'application/zip' => 'attachment'
} unless defined? DISPOSITION
def determine_file_options_for(file_name, original_options = {})
original_options[:type] ||= (FILE_TYPES[File.extname(file_name)] or 'application/octet-stream')
original_options[:disposition] ||= (DISPOSITION[original_options[:type]] or 'attachment')
original_options[:stream] ||= false
original_options[:x_sendfile] = true if request.env.include?('HTTP_X_SENDFILE_TYPE') &&
( request.remote_addr == LOCALHOST || defined?(PhusionPassenger) )
original_options
end
def send_file(file, options = {})
determine_file_options_for(file, options)
super(file, options)
end
def password_check(password)
if password == @web.password
cookies[CGI.escape(@web_name)] = password
true
else
false
end
end
def password_error(password)
if password.nil? or password.empty?
'Please enter the password.'
else
'You entered a wrong password. Please enter the right one.'
end
end
def redirect_home(web = @web_name)
if web
redirect_to_page('HomePage', web)
else
redirect_to '/'
end
end
def redirect_to_page(page_name = @page_name, web = @web_name)
redirect_to :web => web, :controller => 'wiki', :action => 'show',
:id => (page_name or 'HomePage')
end
def remember_location
if request.method == :get and
@status == '200' and not \
%w(locked save back file pic import).include?(action_name)
session[:return_to] = request.request_uri
logger.debug "Session ##{session.object_id}: remembered URL '#{session[:return_to]}'"
end
end
def rescue_action_in_public(exception)
render :status => 500, :text => <<-EOL
<html xmlns="http://www.w3.org/1999/xhtml"><body>
<h2>Internal Error</h2>
<p>An application error occurred while processing your request.</p>
<!-- \n#{exception.to_s.purify.gsub!(/-{2,}/, '- -') }\n#{exception.backtrace.join("\n")}\n -->
</body></html>
EOL
end
def return_to_last_remembered
# Forget the redirect location
redirect_target, session[:return_to] = session[:return_to], nil
tried_home, session[:tried_home] = session[:tried_home], false
# then try to redirect to it
if redirect_target.nil?
if tried_home
raise 'Application could not render the index page'
else
logger.debug("Session ##{session.object_id}: no remembered redirect location, trying home")
redirect_home
end
else
logger.debug("Session ##{session.object_id}: " +
"redirect to the last remembered URL #{redirect_target}")
redirect_to(redirect_target)
end
end
def set_content_type_header
response.charset = 'utf-8'
if %w(atom_with_content atom_with_headlines).include?(action_name)
response.content_type = Mime::ATOM
elsif %w(tex tex_list).include?(action_name)
response.content_type = Mime::TEXT
elsif xhtml_enabled?
if request.user_agent =~ /Validator/ or request.env.include?('HTTP_ACCEPT') &&
Mime::Type.parse(request.env["HTTP_ACCEPT"]).include?(Mime::XHTML)
response.content_type = Mime::XHTML
elsif request.user_agent =~ /MathPlayer/
response.charset = nil
response.content_type = Mime::XHTML
response.extend(MathPlayerHack)
else
response.content_type = Mime::HTML
end
else
response.content_type = Mime::HTML
end
end
def set_robots_metatag
if controller_name == 'wiki' and %w(show published s5).include? action_name and !(params[:mode] == 'diff')
@robots_metatag_value = 'index,follow'
else
@robots_metatag_value = 'noindex,nofollow'
end
end
def setup_url_generator
PageRenderer.setup_url_generator(UrlGenerator.new(self))
end
def teardown_url_generator
PageRenderer.teardown_url_generator
end
def wiki
self.class.wiki
end
private
def in_a_web?
not @web_name.nil?
end
def authorization_needed?
not %w(login authenticate feeds published atom_with_headlines atom_with_content file blahtex_png).include?(action_name)
end
def authorized?
@web.nil? or
@web.password.nil? or
cookies[CGI.escape(@web_name)] == @web.password or
password_check(params['password']) or
(@web.published? and action_name == 's5')
end
def is_post
unless (request.post? || Rails.env.test?)
layout = 'error'
layout = false if %w(tex tex_list).include?(action_name)
headers['Allow'] = 'POST'
render(:status => 405, :text => 'You must use an HTTP POST', :layout => layout)
end
return true
end
end
module Mime
# Fix HTML
#HTML = Type.new "text/html", :html, %w( application/xhtml+xml )
self.class.const_set("HTML", Type.new("text/html", :html) )
# Add XHTML
XHTML = Type.new "application/xhtml+xml", :xhtml
# Fix xhtml and html lookups
LOOKUP["text/html"] = HTML
LOOKUP["application/xhtml+xml"] = XHTML
end
module MathPlayerHack
def charset=(encoding)
self.headers["Content-Type"] = "#{content_type || Mime::HTML}"
end
end
module Instiki
module VERSION #:nodoc:
MAJOR = 0
MINOR = 19
TINY = 3
SUFFIX = '(MML+)'
PRERELEASE = false
if PRERELEASE
STRING = [MAJOR, MINOR].join('.') + PRERELEASE + SUFFIX
else
STRING = [MAJOR, MINOR, TINY].join('.') + SUFFIX
end
end
end
# Monkey patch, to make Hash#key work in Ruby 1.8
class Hash
alias_method(:key, :index) unless method_defined?(:key)
end
# Monkey patch, to ensure ActionCache doesn't muck with the content-type header.
module ActionController #:nodoc:
module Caching
module Actions
class ActionCacheFilter
private
def set_content_type!(controller, extension)
return
end
end
end
end
end

View file

@ -1,40 +0,0 @@
module CacheSweepingHelper
def expire_cached_page(web, page_name)
expire_action :controller => 'wiki', :web => web.address,
:action => %w(show published s5 tex print history source), :id => page_name
expire_action :controller => 'wiki', :web => web.address,
:action => 'show', :id => page_name, :mode => 'diff'
end
def expire_cached_summary_pages(web)
categories = WikiReference.list_categories(web)
%w(recently_revised list).each do |action|
expire_action :controller => 'wiki', :web => web.address, :action => action
categories.each do |category|
expire_action :controller => 'wiki', :web => web.address, :action => action, :category => category
end
end
%w(authors atom_with_content atom_with_headlines file_list).each do |action|
expire_action :controller => 'wiki', :web => web.address, :action => action
end
%w(file_name created_at).each do |sort_order|
expire_action :controller => 'wiki', :web => web.address, :action => 'file_list', :sort_order => sort_order
end
end
def expire_cached_revisions(page)
page.rev_ids.count.times do |i|
revno = i+1
expire_action :controller => 'wiki', :web => page.web.address,
:action => 'revision', :id => page.name, :rev => revno
expire_action :controller => 'wiki', :web => page.web.address,
:action => 'revision', :id => page.name, :rev => revno, :mode => 'diff'
expire_action :controller => 'wiki', :web => page.web.address,
:action => 'source', :id => page.name, :rev => revno
end
end
end

View file

@ -1,69 +1,29 @@
# Controller responsible for serving files and pictures. require 'fileutils'
require 'application'
require 'instiki_errors'
require 'zip/zip' # Controller that is responsible for serving files and pictures.
require 'instiki_stringsupport' # Disabled in version 0.10
class FileController < ApplicationController class FileController < ApplicationController
layout 'default' layout 'default'
before_filter :check_authorized before_filter :check_allow_uploads
before_filter :check_allow_uploads, :dnsbl_check, :except => [:file, :blahtex_png]
def file def file
@file_name = params['id'] check_path
if params['file'] if @params['file']
return unless is_post and check_allow_uploads
# form supplied # form supplied
new_file = @web.wiki_files.create(params['file']) file_yard.upload_file(@file_name, @params['file'])
if new_file.valid?
flash[:info] = "File '#{@file_name}' successfully uploaded" flash[:info] = "File '#{@file_name}' successfully uploaded"
WikiReference.pages_that_link_to_file(@web, @file_name).each do |page| @web.refresh_pages_with_references(@file_name)
RevisionSweeper.expire_page(@web, page) return_to_last_remembered
end elsif file_yard.has_file?(@file_name)
redirect_to(params['referring_page']) send_file(file_yard.file_path(@file_name))
else else
# pass the file with errors back into the form logger.debug("File not found: #{file_yard.files_path}/#{@file_name}")
@file = new_file # go to the template, which is a file upload form
render
end
else
# no form supplied, this is a request to download the file
file = @web.files_path.join(@file_name)
if File.exists?(file)
send_file(file)
else
return unless check_allow_uploads
@file = WikiFile.new(:file_name => @file_name)
render
end
end
end
def blahtex_png
send_file(@web.blahtex_pngs_path.join(params['id']))
end
def delete
@file_name = params['id']
file = WikiFile.find_by_file_name(@file_name)
unless file
flash[:error] = "File '#{@file_name}' not found."
redirect_to_page(@page_name)
end
system_password = params['system_password']
if system_password
return unless is_post
# form supplied
if wiki.authenticate(system_password)
file.destroy
flash[:info] = "File '#{@file_name}' deleted."
else
flash[:error] = "System Password incorrect."
end
redirect_to_page(@page_name)
else
# no system password supplied, display the form
end end
end end
@ -71,15 +31,35 @@ class FileController < ApplicationController
return_to_last_remembered return_to_last_remembered
end end
def pic
check_path
if @params['file']
# form supplied
file_yard.upload_file(@file_name, @params['file'])
@web.refresh_pages_with_references(@file_name)
flash[:info] = "Image '#{@file_name}' successfully uploaded"
return_to_last_remembered
elsif file_yard.has_file?(@file_name)
send_file(file_yard.file_path(@file_name))
else
logger.debug("Image not found: #{file_yard.files_path}/#{@file_name}")
render_action 'file'
end
end
def import def import
if params['file'] return if file_uploads_disabled?
check_authorization
if @params['file']
@problems = [] @problems = []
import_file_name = "#{@web.address}-import-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}.zip" import_file_name = "#{@web.address}-import-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}.zip"
import_from_archive(params['file'].path) file_yard.upload_file(import_file_name, @params['file'])
import_from_archive(file_yard.file_path(import_file_name))
if @problems.empty? if @problems.empty?
flash[:info] = 'Import successfully finished' flash[:info] = 'Import successfully finished'
else else
flash[:error] = 'Import finished, but some pages were not imported:<li>' + flash[:error] = "Import finished, but some pages were not imported:<li>" +
@problems.join('</li><li>') + '</li>' @problems.join('</li><li>') + '</li>'
end end
return_to_last_remembered return_to_last_remembered
@ -90,36 +70,40 @@ class FileController < ApplicationController
protected protected
def check_authorized
unless authorized? or @web.published?
@hide_navigation = true
render(:status => 403, :text => 'This web is private', :layout => true)
end
end
def check_allow_uploads def check_allow_uploads
if @web
if @web.allow_uploads? and authorized? # TODO enable file uploads again after 0.10 release
return true unless RAILS_ENV == 'test'
else render_text 'File uploads are not ready for general use in Instiki 0.10', '403 Forbidden'
@hide_navigation = true
render(:status => 403, :text => 'File uploads are blocked by the webmaster', :layout => true)
return false return false
end end
else
render(:status => 404, :text => "Web #{params['web'].inspect} not found", :layout => 'error') unless @web.allow_uploads
render_text 'File uploads are blocked by the webmaster', '403 Forbidden'
return false
end end
end end
private private
def check_path
raise Instiki::ValidationError.new("Invalid path: no file name") unless @file_name
raise Instiki::ValidationError.new("Invalid path: no web name") unless @web_name
raise Instiki::ValidationError.new("Invalid path: unknown web name") unless @web
end
def file_yard
@wiki.file_yard(@web)
end
def import_from_archive(archive) def import_from_archive(archive)
logger.info "Importing pages from #{archive}" logger.info "Importing pages from #{archive}"
zip = Zip::ZipInputStream.open(archive) zip = Zip::ZipInputStream.open(archive)
while (entry = zip.get_next_entry) do while (entry = zip.get_next_entry) do
ext_length = File.extname(entry.name).length ext_length = File.extname(entry.name).length
page_name = entry.name[0..-(ext_length + 1)].purify page_name = entry.name[0..-(ext_length + 1)]
page_content = entry.get_input_stream.read.purify page_content = entry.get_input_stream.read
logger.info "Processing page '#{page_name}'" logger.info "Processing page '#{page_name}'"
begin begin
existing_page = @wiki.read_page(@web.address, page_name) existing_page = @wiki.read_page(@web.address, page_name)
@ -129,10 +113,10 @@ class FileController < ApplicationController
next next
else else
logger.info "Page '#{page_name}' already exists. Adding a new revision to it." logger.info "Page '#{page_name}' already exists. Adding a new revision to it."
wiki.revise_page(@web.address, page_name, page_name, page_content, Time.now, @author, PageRenderer.new) wiki.revise_page(@web.address, page_name, page_content, Time.now, @author)
end end
else else
wiki.write_page(@web.address, page_name, page_content, Time.now, @author, PageRenderer.new) wiki.write_page(@web.address, page_name, page_content, Time.now, @author)
end end
rescue => e rescue => e
logger.error(e) logger.error(e)

View file

@ -1,50 +0,0 @@
require_dependency 'cache_sweeping_helper'
class RevisionSweeper < ActionController::Caching::Sweeper
include CacheSweepingHelper
observe Revision, Page
def before_save(record)
if record.is_a?(Revision)
expire_cached_page(record.page.web, record.page.name)
expire_cached_revisions(record.page)
end
end
def after_save(record)
if record.is_a?(Revision)
expire_caches(record.page)
end
end
def after_create(record)
if record.is_a?(Page)
WikiReference.pages_that_reference(record.web, record.name).each do |page_name|
expire_cached_page(record.web, page_name)
end
end
end
def after_delete(record)
if record.is_a?(Page)
expire_caches(record)
end
end
def self.expire_page(web, page_name)
new.expire_cached_page(web, page_name)
end
private
def expire_caches(page)
expire_cached_summary_pages(page.web)
pages_to_expire = ([page.name] +
WikiReference.pages_redirected_to(page.web, page.name) +
WikiReference.pages_that_include(page.web, page.name)).uniq
pages_to_expire.each { |page_name| expire_cached_page(page.web, page_name) }
end
end

View file

@ -1,34 +0,0 @@
require_dependency 'cache_sweeping_helper'
class WebSweeper < ActionController::Caching::Sweeper
include CacheSweepingHelper
observe Web, Page, WikiFile
def after_save(record)
if record.is_a?(Web)
web = record
web.pages.find_each { |page| expire_cached_page(web, page.name) }
expire_cached_summary_pages(web)
elsif record.is_a?(WikiFile)
record.web.pages_that_link_to_file(record.file_name).each do |page|
expire_cached_page(record.web, page)
end
expire_cached_summary_pages(record.web)
end
end
def after_destroy(record)
if record.is_a?(Web)
expire_cached_summary_pages(record)
elsif record.is_a?(Page)
expire_cached_page(record.web, record.name)
expire_cached_summary_pages(record.web)
expire_cached_revisions(record)
else
expire_cached_summary_pages(record.web)
end
end
end

View file

@ -1,26 +1,18 @@
require 'application'
require 'fileutils' require 'fileutils'
require 'maruku' require 'redcloth_for_tex'
require 'maruku/ext/math' require 'parsedate'
require 'zip/zip'
require 'instiki_stringsupport'
require 'resolv'
class WikiController < ApplicationController class WikiController < ApplicationController
before_filter :load_page layout 'default', :except => [:rss_feed, :rss_with_content, :rss_with_headlines, :tex, :export_tex, :export_html]
before_filter :dnsbl_check, :only => [:edit, :new, :save, :export_html, :export_markup]
caches_action :show, :published, :authors, :tex, :s5, :print, :recently_revised, :list, :file_list, :source,
:history, :revision, :atom_with_content, :atom_with_headlines, :if => Proc.new { |c| c.send(:do_caching?) }
cache_sweeper :revision_sweeper
layout 'default', :except => [:atom_with_content, :atom_with_headlines, :atom, :source, :tex, :s5, :export_html]
def index def index
if @web_name if @web_name
redirect_home redirect_home
elsif not @wiki.setup? elsif not @wiki.setup?
redirect_to :controller => 'admin', :action => 'create_system' redirect_to :controller => 'admin', :action => 'create_system'
elsif @wiki.webs.size == 1 elsif @wiki.webs.length == 1
redirect_home @wiki.webs.values.first.address redirect_home @wiki.webs.values.first.address
else else
redirect_to :action => 'web_list' redirect_to :action => 'web_list'
@ -30,10 +22,10 @@ class WikiController < ApplicationController
# Outside a single web -------------------------------------------------------- # Outside a single web --------------------------------------------------------
def authenticate def authenticate
if password_check(params['password']) if password_check(@params['password'])
redirect_home redirect_home
else else
flash[:info] = password_error(params['password']) flash[:info] = password_error(@params['password'])
redirect_to :action => 'login', :web => @web_name redirect_to :action => 'login', :web => @web_name
end end
end end
@ -50,84 +42,14 @@ class WikiController < ApplicationController
# Within a single web --------------------------------------------------------- # Within a single web ---------------------------------------------------------
def authors def authors
@page_names_by_author = @web.page_names_by_author @authors = @web.select.authors.sort
@authors = @page_names_by_author.keys.sort
end
def file_list
sort_order = params['sort_order'] || 'file_name'
case sort_order
when 'file_name'
@alt_sort_order = 'created_at'
@alt_sort_name = 'date'
else
@alt_sort_order = 'file_name'
@alt_sort_name = 'filename'
end
@file_list = @web.file_list(sort_order)
end end
def export_html def export_html
export_pages_as_zip(html_ext) do |page| export_pages_as_zip('html') do |page|
renderer = PageRenderer.new(page.current_revision) @page = page
rendered_page = <<-EOL @link_mode = :export
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg-flat.dtd" > render_to_string('wiki/print', use_layout = (@params['layout'] != 'no'))
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>#{page.plain_name} in #{@web.name}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="public/javascripts/page_helper.js" type="text/javascript"></script>
<link href="public/stylesheets/instiki.css" media="all" rel="stylesheet" type="text/css" />
<link href="public/stylesheets/syntax.css" media="all" rel="stylesheet" type="text/css" />
<style type="text/css">
h1#pageName, div.info, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover, [actiontype="toggle"]:hover, #TextileHelp h3 {
color: ##{@web ? @web.color : "393"};
}
a:visited.existingWikiWord {
color: ##{darken(@web ? @web.color : "393")};
}
</style>
<style type="text/css"><!--/*--><![CDATA[/*><!--*/
#{@web ? @web.additional_style : ''}
/*]]>*/--></style>
<script src="public/javascripts/prototype.js" type="text/javascript"></script>
<script src="public/javascripts/effects.js" type="text/javascript"></script>
<script src="public/javascripts/dragdrop.js" type="text/javascript"></script>
<script src="public/javascripts/controls.js" type="text/javascript"></script>
<script src="public/javascripts/application.js" type="text/javascript"></script>
</head>
<body>
<div id="Container">
<div id="Content">
<h1 id="pageName">
#{xhtml_enabled? ? %{<span id="svg_logo"><svg version="1.1" width="100%" height="100%" viewBox='0 -1 180 198' xmlns='http://www.w3.org/2000/svg'>
<path id="svg_logo_path" fill="##{@web ? @web.color : "393"}" stroke-width='0.5' stroke='#000' d='
M170,60c4,11-1,20-12,25c-9,4-25,3-20,15c5,5,15,0,24,1c11,1,21,11,14,21c-10,15-35,6-48-1c-5-3-27-23-32-10c-1,13,15,10,22,16
c11,4,24,14,34,20c12,10,7,25-9,23c-11-1-22-9-30-16c-5-5-13-18-21-9c-2,6,2,11,5,14c9,9,22,14,22,31c-2,8-12,8-18,4c-4-3-9-8-11-13
c-3-6-5-18-12-18c-14-1-5,28-18,30c-9,2-13-9-12-16c1-14,12-24,21-31c5-4,17-13,10-20c-9-10-19,12-23,16c-7,7-17,16-31,15
c-9-1-18-9-11-17c5-7,14-4,23-6c6-1,15-8,8-15c-5-6-57,2-42-24c7-12,51,4,61,6c6,1,17,4,18-4c2-11-12-7-21-8c-21-2-49-14-49-34
c0-5,3-11,8-11C31,42,34,65,42,67c6,1,9-3,8-9C49,49,38,40,40,25c1-5,4-15,13-14c10,2,11,18,13,29c1,8,0,24,7,28c15,0,5-22,4-30
C74,23,78,7,87,1c8-4,14,1,16,9c2,11-8,21-2,30c8,2,11-6,14-12c9-14,36-18,30,5c-3,9-12,19-21,24c-6,4-22,10-23,19c-2,14,15,2,18-2
c9-9,20-18,33-22C159,52,166,54,170,60' />
</svg></span>} : ''}
<span class="webName">#{@web.name}</span><br />
#{page.plain_name}
</h1>
#{renderer.display_content_for_export}
<div class="byline">
#{page.revisions? ? "Revised" : "Created" } on #{ page.revised_at.strftime('%B %d, %Y %H:%M:%S') }
by
#{ UrlGenerator.new(self).make_link(@web, page.author.name, @web, nil, { :mode => :export }) }
</div>
</div>
</div>
</body>
</html>
EOL
rendered_page
end end
end end
@ -135,21 +57,21 @@ EOL
export_pages_as_zip(@web.markup) { |page| page.content } export_pages_as_zip(@web.markup) { |page| page.content }
end end
# def export_pdf def export_pdf
# file_name = "#{@web.address}-tex-#{@web.revised_at.strftime('%Y-%m-%d-%H-%M-%S')}" file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}"
# file_path = File.join(@wiki.storage_path, file_name) file_path = File.join(@wiki.storage_path, file_name)
#
# export_web_to_tex "#{file_path}.tex" unless FileTest.exists? "#{file_path}.tex"
# convert_tex_to_pdf "#{file_path}.tex"
# send_file "#{file_path}.pdf"
# end
# def export_tex export_web_to_tex "#{file_path}.tex" unless FileTest.exists? "#{file_path}.tex"
# file_name = "#{@web.address}-tex-#{@web.revised_at.strftime('%Y-%m-%d-%H-%M-%S')}.tex" convert_tex_to_pdf "#{file_path}.tex"
# file_path = File.join(@wiki.storage_path, file_name) send_file "#{file_path}.pdf"
# export_web_to_tex(file_path) unless FileTest.exists?(file_path) end
# send_file file_path
# end def export_tex
file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}.tex"
file_path = File.join(@wiki.storage_path, file_name)
export_web_to_tex(file_path) unless FileTest.exists?(file_path)
send_file file_path
end
def feeds def feeds
@rss_with_content_allowed = rss_with_content_allowed? @rss_with_content_allowed = rss_with_content_allowed?
@ -158,6 +80,7 @@ EOL
def list def list
parse_category parse_category
@pages_by_name = @pages_in_category.by_name
@page_names_that_are_wanted = @pages_in_category.wanted_pages @page_names_that_are_wanted = @pages_in_category.wanted_pages
@pages_that_are_orphaned = @pages_in_category.orphaned_pages @pages_that_are_orphaned = @pages_in_category.orphaned_pages
end end
@ -165,53 +88,23 @@ EOL
def recently_revised def recently_revised
parse_category parse_category
@pages_by_revision = @pages_in_category.by_revision @pages_by_revision = @pages_in_category.by_revision
@pages_by_day = Hash.new { |h, day| h[day] = [] }
@pages_by_revision.each do |page|
day = Date.new(page.revised_at.year, page.revised_at.month, page.revised_at.day)
@pages_by_day[day] << page
end
end end
def atom_with_content def rss_with_content
if rss_with_content_allowed? if rss_with_content_allowed?
render_atom(hide_description = false) render_rss(hide_description = false, *parse_rss_params)
else else
render :text => 'Atom feed with content for this web is blocked for security reasons. ' + render_text 'RSS feed with content for this web is blocked for security reasons. ' +
'The web is password-protected and not published', :status => 403, :layout => 'error' 'The web is password-protected and not published', '403 Forbidden'
end end
end end
def atom_with_headlines def rss_with_headlines
render_atom(hide_description = true) render_rss(hide_description = true, *parse_rss_params)
end end
def tex_list
return unless is_post
if [:markdownMML, :markdownPNG, :markdown].include?(@web.markup)
@tex_content = ''
# Ruby 1.9.x has ordered hashes; 1.8.x doesn't. So let's just parse the query ourselves.
ordered_params = ActiveSupport::OrderedHash[*request.raw_post.split('&').collect {|k_v| k_v.split('=').collect {|x| CGI::unescape(x)}}.flatten]
ordered_params.each do |name, p|
if p == 'tex' && @web.has_page?(name)
@tex_content << "\\section*\{#{Maruku.new(name).to_latex.strip}\}\n\n"
@tex_content << Maruku.new(@web.page(name).content).to_latex
end
end
else
@tex_content = 'TeX export only supported with the Markdown text filters.'
end
if @tex_content == ''
flash[:error] = "You didn't select any pages to export."
redirect_to :back
return
end
expire_action :controller => 'wiki', :web => @web.address, :action => 'list', :category => params['category']
render(:layout => 'tex')
end
def search def search
@query = params['query'] ? params['query'].purify : '' @query = @params['query']
@title_results = @web.select { |page| page.name =~ /#{@query}/i }.sort @title_results = @web.select { |page| page.name =~ /#{@query}/i }.sort
@results = @web.select { |page| page.content =~ /#{@query}/i }.sort @results = @web.select { |page| page.content =~ /#{@query}/i }.sort
all_pages_found = (@results + @title_results).uniq all_pages_found = (@results + @title_results).uniq
@ -230,7 +123,7 @@ EOL
def edit def edit
if @page.nil? if @page.nil?
redirect_home redirect_home
elsif @page.locked?(Time.now) and not params['break_lock'] elsif @page.locked?(Time.now) and not @params['break_lock']
redirect_to :web => @web_name, :action => 'locked', :id => @page_name redirect_to :web => @web_name, :action => 'locked', :id => @page_name
else else
@page.lock(Time.now, @author) @page.lock(Time.now, @author)
@ -245,102 +138,64 @@ EOL
# to template # to template
end end
# def pdf def pdf
# page = wiki.read_page(@web_name, @page_name) page = wiki.read_page(@web_name, @page_name)
# safe_page_name = @page.name.gsub(/\W/, '') safe_page_name = @page.name.gsub(/\W/, '')
# file_name = "#{safe_page_name}-#{@web.address}-#{@page.revised_at.strftime('%Y-%m-%d-%H-%M-%S')}" file_name = "#{safe_page_name}-#{@web.address}-#{@page.created_at.strftime('%Y-%m-%d-%H-%M-%S')}"
# file_path = File.join(@wiki.storage_path, file_name) file_path = File.join(@wiki.storage_path, file_name)
#
# export_page_to_tex("#{file_path}.tex") unless FileTest.exists?("#{file_path}.tex") export_page_to_tex("#{file_path}.tex") unless FileTest.exists?("#{file_path}.tex")
# # NB: this is _very_ slow # NB: this is _very_ slow
# convert_tex_to_pdf("#{file_path}.tex") convert_tex_to_pdf("#{file_path}.tex")
# send_file "#{file_path}.pdf" send_file "#{file_path}.pdf"
# end end
def print def print
if @page.nil?
redirect_home
end
@link_mode ||= :show @link_mode ||= :show
@renderer = PageRenderer.new(@page.current_revision)
# to template # to template
end end
def published def published
if not @web.published? if @web.published
render(:text => "Published version of web '#{@web_name}' is not available", :status => 404, :layout => 'error') @page = wiki.read_page(@web_name, @page_name || 'HomePage')
return
end
@page_name ||= 'HomePage'
@page ||= wiki.read_page(@web_name, @page_name)
@link_mode ||= :publish
if @page
@renderer = PageRenderer.new(@page.current_revision)
else else
real_page = WikiReference.page_that_redirects_for(@web, @page_name) redirect_home
if real_page
flash[:info] = "Redirected from \"#{@page_name}\"."
redirect_to :web => @web_name, :action => 'published', :id => real_page, :status => 301
else
render(:text => "Page '#{@page_name}' not found", :status => 404, :layout => 'error')
end
end end
end end
def revision def revision
get_page_and_revision get_page_and_revision
@show_diff = (params[:mode] == 'diff')
@renderer = PageRenderer.new(@revision)
end end
def rollback def rollback
get_page_and_revision get_page_and_revision
if @page.locked?(Time.now) and not params['break_lock']
redirect_to :web => @web_name, :action => 'locked', :id => @page_name
else
@page.lock(Time.now, @author)
end
end end
def save def save
render(:status => 404, :text => 'Undefined page name', :layout => 'error') and return if @page_name.nil? redirect_home if @page_name.nil?
return unless is_post cookies['author'] = @params['author']
author_name = params['author'].purify
author_name = 'AnonymousCoward' if author_name =~ /^\s*$/
begin begin
the_content = params['content'].purify check_for_spam(@params['content'], remote_ip)
prev_content = '' check_blocked_ips(remote_ip)
filter_spam(the_content)
cookies['author'] = { :value => author_name.dup.as_bytes, :expires => Time.utc(2030) }
if @page if @page
new_name = params['new_name'] ? params['new_name'].purify : @page_name wiki.revise_page(@web_name, @page_name, @params['content'], Time.now,
new_name = @page_name if new_name.empty? Author.new(@params['author'], remote_ip))
prev_content = @page.current_revision.content
raise Instiki::ValidationError.new('A page named "' + new_name.escapeHTML + '" already exists.') if
@page_name != new_name && @web.has_page?(new_name)
wiki.revise_page(@web_name, @page_name, new_name, the_content, Time.now,
Author.new(author_name, remote_ip), PageRenderer.new)
@page.unlock @page.unlock
@page_name = new_name
else else
wiki.write_page(@web_name, @page_name, the_content, Time.now, wiki.write_page(@web_name, @page_name, @params['content'], Time.now,
Author.new(author_name, remote_ip), PageRenderer.new) Author.new(@params['author'], remote_ip))
end end
redirect_to_page @page_name redirect_to_page @page_name
rescue Instiki::ValidationError => e rescue => e
flash[:error] = e.to_s flash[:error] = e
logger.error e flash[:content] = @params['content']
param_hash = {:web => @web_name, :id => @page_name}
# Work around Rails bug: flash will not display if query string is longer than 10192 bytes
param_hash.update( :content => the_content ) if the_content &&
CGI::escape(the_content).length < 10183 && the_content != prev_content
if @page if @page
@page.unlock @page.unlock
redirect_to param_hash.update( :action => 'edit' ) redirect_to :action => 'edit', :web => @web_name, :id => @page_name
else else
redirect_to param_hash.update( :action => 'new' ) redirect_to :action => 'new', :web => @web_name, :id => @page_name
end end
end end
end end
@ -348,225 +203,177 @@ EOL
def show def show
if @page if @page
begin begin
@renderer = PageRenderer.new(@page.current_revision) render_action 'page'
@show_diff = (params[:mode] == 'diff')
render :action => 'page'
# TODO this rescue should differentiate between errors due to rendering and errors in # TODO this rescue should differentiate between errors due to rendering and errors in
# the application itself (for application errors, it's better not to rescue the error at all) # the application itself (for application errors, it's better not to rescue the error at all)
rescue => e rescue => e
logger.error e logger.error e
flash[:error] = e.to_s flash[:error] = e.message
if in_a_web? if in_a_web?
redirect_to :action => 'edit', :web => @web_name, :id => @page_name redirect_to :action => 'edit', :web => @web_name, :id => @page_name
else else
raise e raise e
end end
end end
else
if not @page_name.nil? and not @page_name.empty?
real_page = WikiReference.page_that_redirects_for(@web, @page_name)
if real_page
flash[:info] = "Redirected from \"#{@page_name}\"."
redirect_to :web => @web_name, :action => 'show', :id => real_page, :status => 301
else
flash[:info] = "Page \"#{@page_name}\" does not exist.\n" +
"Please create it now, or hit the \"back\" button in your browser."
redirect_to :web => @web_name, :action => 'new', :id => @page_name
end
else
render :text => 'Page name is not specified', :status => 404, :layout => 'error'
end
end
end
def history
if @page
@revisions_by_day = Hash.new { |h, day| h[day] = [] }
@revision_numbers = Hash.new { |h, id| h[id] = [] }
revision_number = @page.rev_ids.size
@page.rev_ids.reverse.each do |rev|
day = Date.new(rev.revised_at.year, rev.revised_at.month, rev.revised_at.day)
@revisions_by_day[day] << rev
@revision_numbers[rev.id] = revision_number
revision_number = revision_number - 1
end
render :action => 'history'
else else
if not @page_name.nil? and not @page_name.empty? if not @page_name.nil? and not @page_name.empty?
redirect_to :web => @web_name, :action => 'new', :id => @page_name redirect_to :web => @web_name, :action => 'new', :id => @page_name
else else
render :text => 'Page name is not specified', :status => 404, :layout => 'error' render_text 'Page name is not specified', '404 Not Found'
end end
end end
end end
def source
@revision = @page.revisions[params['rev'].to_i - 1] if params['rev']
end
def tex def tex
if [:markdownMML, :markdownPNG, :markdown].include?(@web.markup) @tex_content = RedClothForTex.new(@page.content).to_tex
@tex_content = Maruku.new(@page.content).to_latex
else
@tex_content = 'TeX export only supported with the Markdown text filters.'
end
render(:layout => 'tex')
end end
def s5
if [:markdownMML, :markdownPNG, :markdown].include?(@web.markup)
my_rendered = PageRenderer.new(@page.current_revision)
@s5_content = my_rendered.display_s5
@s5_theme = my_rendered.s5_theme
else
@s5_content = "S5 not supported with this text filter"
@s5_theme = "default"
end
end
protected
def do_caching?
flash.empty?
end
def load_page
@page_name = params['id'] ? params['id'].purify : nil
@page = @wiki.read_page(@web_name, @page_name) if @page_name
end
private private
# def convert_tex_to_pdf(tex_path) def check_blocked_ips(ip)
# # TODO remove earlier PDF files with the same prefix if defined? BLOCKED_IPS and not BLOCKED_IPS.nil?
# # TODO handle gracefully situation where pdflatex is not available BLOCKED_IPS.each do |blocked_ip|
# begin raise Instiki::ValidationError.new('Revision rejected by spam filter') if ip == blocked_ip
# wd = Dir.getwd end
# Dir.chdir(File.dirname(tex_path)) end
# logger.info `pdflatex --interaction=nonstopmode #{File.basename(tex_path)}` end
# ensure
# Dir.chdir(wd) def check_for_spam(new_content, ip)
# end if defined? SPAM_PATTERNS and not SPAM_PATTERNS.nil?
# end SPAM_PATTERNS.each do |pattern|
if new_content =~ pattern
logger.info "Spam attempt from IP address #{ip}"
raise Instiki::ValidationError.new('Revision rejected by spam filter')
end
end
end
end
def convert_tex_to_pdf(tex_path)
# TODO remove earlier PDF files with the same prefix
# TODO handle gracefully situation where pdflatex is not available
begin
wd = Dir.getwd
Dir.chdir(File.dirname(tex_path))
logger.info `pdflatex --interaction=nonstopmode #{File.basename(tex_path)}`
ensure
Dir.chdir(wd)
end
end
def export_page_to_tex(file_path) def export_page_to_tex(file_path)
if @web.markup == :markdownMML || @web.markup == :markdownPNG tex
@tex_content = Maruku.new(@page.content).to_latex File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex')) }
else
@tex_content = 'TeX export only supported with the Markdown text filters.'
end
File.open(file_path, 'w') { |f| f.write(render_to_string(:template => 'wiki/tex', :layout => 'tex')) }
end end
def export_pages_as_zip(file_type, &block) def export_pages_as_zip(file_type, &block)
file_prefix = "#{@web.address}-#{file_type}-" file_prefix = "#{@web.address}-#{file_type}-"
timestamp = @web.revised_at.strftime('%Y-%m-%d-%H-%M-%S') timestamp = @web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')
file_path = @wiki.storage_path.join(file_prefix + timestamp + '.zip') file_path = File.join(@wiki.storage_path, file_prefix + timestamp + '.zip')
tmp_path = "#{file_path}.tmp" tmp_path = "#{file_path}.tmp"
Zip::ZipFile.open(tmp_path, Zip::ZipFile::CREATE) do |zip_out| Zip::ZipOutputStream.open(tmp_path) do |zip_out|
@web.select.by_name.each do |page| @web.select.by_name.each do |page|
zip_out.get_output_stream("#{CGI.escape(page.name)}.#{file_type}") do |f| zip_out.put_next_entry("#{CGI.escape(page.name)}.#{file_type}")
f.puts(block.call(page)) zip_out.puts(block.call(page))
end
# add an index file, if exporting to HTML
if file_type.to_s.downcase == 'html'
zip_out.put_next_entry 'index.html'
zip_out.puts "<html><head>" +
"<META HTTP-EQUIV=\"Refresh\" CONTENT=\"0;URL=HomePage.#{file_type}\"></head></html>"
end end
end end
# add an index file, and the stylesheet and javascript directories, if exporting to HTML FileUtils.rm_rf(Dir[File.join(@wiki.storage_path, file_prefix + '*.zip')])
if file_type.to_s.downcase == html_ext
zip_out.get_output_stream("index.#{html_ext}") do |f|
f.puts "<html xmlns='http://www.w3.org/1999/xhtml'><head>" +
"<meta http-equiv=\"Refresh\" content=\"0;URL=HomePage.#{html_ext}\" /></head></html>"
end
dir = Rails.root.join('public')
Dir["#{dir}/{images,javascripts,s5,stylesheets}/**/*"].each do |f|
zip_out.add "public#{f.sub(dir.to_s,'')}", f
end
end
files = @web.files_path
Dir["#{files}/**/*"].each do |f|
zip_out.add "files#{f.sub(files.to_s,'')}", f
end
end
FileUtils.rm_rf(Dir[@wiki.storage_path.join(file_prefix + '*.zip').to_s])
FileUtils.mv(tmp_path, file_path) FileUtils.mv(tmp_path, file_path)
send_file file_path send_file file_path
end end
# def export_web_to_tex(file_path) def export_web_to_tex(file_path)
# if @web.markup == :markdownMML @tex_content = table_of_contents(@web.pages['HomePage'].content, render_tex_web)
# @tex_content = Maruku.new(@page.content).to_latex File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex_web')) }
# else end
# @tex_content = 'TeX export only supported with the Markdown text filters.'
# end
# @tex_content = table_of_contents(@web.page('HomePage').content, render_tex_web)
# File.open(file_path, 'w') { |f| f.write(render_to_string(:template => 'wiki/tex_web', :layout => tex)) }
# end
def get_page_and_revision def get_page_and_revision
if params['rev'] revision_index = (@params['rev'] || 0).to_i
@revision_number = params['rev'].to_i if @page.nil? or @page.revisions[revision_index].nil?
render_text 'Page not found', '404 Not Found'
else else
@revision_number = @page.rev_ids.size @revision = @page.revisions[revision_index]
end end
@revision = @page.revisions[@revision_number - 1]
end end
def parse_category def parse_category
@categories = WikiReference.list_categories(@web).sort @categories = @web.categories
@category = params['category'] @category = @params['category']
if @category if @categories.include?(@category)
@pages_in_category = @web.select { |page| page.in_category?(@category) }
@set_name = "category '#{@category}'" @set_name = "category '#{@category}'"
pages = WikiReference.pages_in_category(@web, @category).sort.map { |page_name| @web.page(page_name) }
@pages_in_category = PageSet.new(@web, pages)
else else
# no category specified, return all pages of the web @pages_in_category = PageSet.new(@web).by_name
@pages_in_category = @web.select_all.by_name
@set_name = 'the web' @set_name = 'the web'
end end
end end
def remote_ip def parse_rss_params
ip = request.remote_ip if @params.include? 'limit'
logger.info(ip) limit = @params['limit'].to_i rescue nil
ip.dup.gsub!(Regexp.union(Resolv::IPv4::Regex, Resolv::IPv6::Regex), '\0') || 'bogus address' limit = nil if limit == 0
else
limit = 15
end
start_date = Time.local(*ParseDate::parsedate(@params['start'])) rescue nil
end_date = Time.local(*ParseDate::parsedate(@params['end'])) rescue nil
[ limit, start_date, end_date ]
end end
def render_atom(hide_description = false, limit = 15) def remote_ip
ip = @request.remote_ip
logger.info(ip)
ip
end
def render_rss(hide_description = false, limit = 15, start_date = nil, end_date = nil)
if limit && !start_date && !end_date
@pages_by_revision = @web.select.by_revision.first(limit) @pages_by_revision = @web.select.by_revision.first(limit)
else
@pages_by_revision = @web.select.by_revision
@pages_by_revision.reject! { |page| page.created_at < start_date } if start_date
@pages_by_revision.reject! { |page| page.created_at > end_date } if end_date
end
@hide_description = hide_description @hide_description = hide_description
@response.headers['Content-Type'] = 'text/xml'
@link_action = @web.password ? 'published' : 'show' @link_action = @web.password ? 'published' : 'show'
render :action => 'atom'
render 'wiki/rss_feed'
end end
def render_tex_web def render_tex_web
@web.select.by_name.inject({}) do |tex_web, page| @web.select.by_name.inject({}) do |tex_web, page|
if @web.markup == :markdownMML || @web.markup == :markdownPNG tex_web[page.name] = RedClothForTex.new(page.content).to_tex
tex_web[page.name] = Maruku.new(page.content).to_latex
else
tex_web[page.name] = 'TeX export only supported with the Markdown text filters.'
end
tex_web tex_web
end end
end end
def rss_with_content_allowed? def render_to_string(template_name, with_layout = false)
@web.password.nil? or @web.published? add_variables_to_assigns
end self.assigns['content_for_layout'] = @template.render_file(template_name)
if with_layout
def filter_spam(content) @template.render_file('layouts/default')
@@spam_patterns ||= load_spam_patterns
@@spam_patterns.each do |pattern|
raise Instiki::ValidationError.new("Your edit was blocked by spam filtering") if content =~ pattern
end
end
def load_spam_patterns
spam_patterns_file = Rails.root.join('config', 'spam_patterns.txt')
if File.exists?(spam_patterns_file)
spam_patterns_file.readlines.inject([]) { |patterns, line| patterns << Regexp.new(line.chomp, Regexp::IGNORECASE) }
else else
[] self.assigns['content_for_layout']
end end
end end
def rss_with_content_allowed?
@web.password.nil? or @web.published
end
def truncate(text, length = 30, truncate_string = '...')
if text.length > length then text[0..(length - 3)] + truncate_string else text end
end
end end

View file

@ -1,6 +1,5 @@
# The methods added to this helper will be available to all templates in the application. # The methods added to this helper will be available to all templates in the application.
module ApplicationHelper module ApplicationHelper
require 'instiki_stringsupport'
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
# where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
@ -20,18 +19,18 @@ require 'instiki_stringsupport'
container = container.to_a if Hash === container container = container.to_a if Hash === container
html_options = container.inject([]) do |options, element| html_options = container.inject([]) do |options, element|
if element.is_a? Array if element.respond_to?(:first) && element.respond_to?(:last)
if element.last != selected if element.last != selected
options << "<option value=\"#{element.last}\">#{element.first}</option>" options << "<option value=\"#{element.last}\">#{element.first}</option>"
else else
options << "<option value=\"#{element.last}\" selected=\"selected\">#{element.first}</option>" options << "<option value=\"#{element.last}\" selected>#{element.first}</option>"
end end
else else
options << ((element != selected) ? "<option>#{element}</option>" : "<option selected>#{element}</option>") options << ((element != selected) ? "<option>#{element}</option>" : "<option selected>#{element}</option>")
end end
end end
html_options.join("\n").html_safe html_options.join("\n")
end end
# Creates a hyperlink to a Wiki page, without checking if the page exists or not # Creates a hyperlink to a Wiki page, without checking if the page exists or not
@ -39,39 +38,13 @@ require 'instiki_stringsupport'
link_to( link_to(
text || page.plain_name, text || page.plain_name,
{:web => @web.address, :action => 'show', :id => page.name, :only_path => true}, {:web => @web.address, :action => 'show', :id => page.name, :only_path => true},
html_options).html_safe html_options)
end end
# Creates a hyperlink to a Wiki page, or to a "new page" form if the page doesn't exist yet # Creates a hyperlink to a Wiki page, or to a "new page" form if the page doesn't exist yet
def link_to_page(page_name, web = @web, text = nil, options = {}) def link_to_page(page_name, web = @web, text = nil, options = {})
raise 'Web not defined' if web.nil? raise 'Web not defined' if web.nil?
UrlGenerator.new(@controller).make_link(@web, page_name, web, text, web.make_link(page_name, text, options.merge(:base_url => "#{base_url}/#{web.address}"))
options.merge(:base_url => "#{base_url}/#{web.address}")).html_safe
end
def author_link(page, options = {})
UrlGenerator.new(@controller).make_link(@web, page.author.name, page.web, nil, options).purify.html_safe
end
# Create a hyperlink to a particular revision of a Wiki page
def link_to_revision(page, revision_number, text = nil, mode = nil, html_options = {})
revision_number == page.rev_ids.size ?
link_to(
text || page.plain_name,
{:web => @web.address, :action => 'show', :id => page.name,
:mode => mode}, html_options).html_safe :
link_to(
text || page.plain_name + "(rev # #{revision_number})".html_safe,
{:web => @web.address, :action => 'revision', :id => page.name,
:rev => revision_number, :mode => mode}, html_options).html_safe
end
# Create a hyperlink to the history of a particular Wiki page
def link_to_history(page, text = nil, html_options = {})
link_to(
text || page.plain_name + "(history)".html_safe,
{:web => @web.address, :action => 'history', :id => page.name},
html_options).html_safe
end end
def base_url def base_url
@ -84,47 +57,19 @@ require 'instiki_stringsupport'
if @categories.empty? if @categories.empty?
'' ''
else else
("<div id=\"categories\">\n" + "<div id=\"categories\">\n" +
'<strong>Categories</strong>:' + '<strong>Categories</strong>:' +
'[' + link_to_unless_current('Any', :web => @web.address, :action => self.action_name, :category => nil) + "]\n" + '[' + link_to_unless_current('Any', :web => @web.address, :action => @action_name) + "]\n" +
@categories.map { |c| @categories.map { |c|
link_to_unless_current(c.html_safe, :web => @web.address, :action => self.action_name, :category => c) link_to_unless_current(c, :web => @web.address, :action => @action_name, :category => c)
}.join(', ') + "\n" + }.join(', ') + "\n" +
'</div>').html_safe '</div>'
end end
end end
# Performs HTML escaping on text, but keeps linefeeds intact (by replacing them with <br/>) # Performs HTML escaping on text, but keeps linefeeds intact (by replacing them with <br/>)
def escape_preserving_linefeeds(text) def escape_preserving_linefeeds(text)
h(text).gsub(/\n/, '<br/>').as_utf8.html_safe h(text).gsub(/\n/, '<br/>')
end
def format_date(date, include_time = true)
# Must use DateTime because Time doesn't support %e on at least some platforms
if include_time
DateTime.new(date.year, date.mon, date.day, date.hour, date.min, date.sec).strftime("%B %e, %Y %H:%M:%S")
else
DateTime.new(date.year, date.mon, date.day).strftime("%B %e, %Y")
end
end
def rendered_content(page)
PageRenderer.new(page.current_revision).display_content
end
def truncate(text, *args)
options = args.extract_options!
options.reverse_merge!(:length => 30, :omission => "...")
return text.html_safe if text.num_chars <= options[:length]
len = options[:length] - options[:omission].as_utf8.num_chars
t = ''
text.split.collect do |word|
if t.num_chars + word.num_chars <= len
t << word + ' '
else
return (t.chop + options[:omission]).html_safe
end
end
end end
end end

View file

@ -1,94 +0,0 @@
module WikiHelper
def navigation_menu_for_revision
menu = []
menu << forward
menu << back_for_revision if @revision_number > 1
menu << current_revision
menu << see_or_hide_changes_for_revision if @revision_number > 1
menu << history if @page.rev_ids.size > 1
menu << rollback
menu
end
def navigation_menu_for_page
menu = []
menu << edit_page
menu << edit_web if @page.name == "HomePage"
if @page.rev_ids.size > 1
menu << back_for_page
menu << see_or_hide_changes_for_page
end
menu << history if @page.rev_ids.size > 1
menu
end
def edit_page
link_text = (@page.name == "HomePage" ? 'Edit Page' : 'Edit')
link_to(link_text, {:web => @web.address, :action => 'edit', :id => @page.name},
{:class => 'navlink', :accesskey => 'E', :id => 'edit', :rel => 'nofollow'})
end
def edit_web
link_to('Edit Web', {:web => @web.address, :action => 'edit_web'},
{:class => 'navlink', :accesskey => 'W', :id => 'edit_web', :rel => 'nofollow'})
end
def history
link_to_history(@page, 'History',
{:class => 'navlink', :accesskey => 'S', :id => 'history', :rel => 'nofollow'})
end
def forward
if @revision_number < @page.rev_ids.size - 1
link_to('Forward in time',
{:web => @web.address, :action => 'revision', :id => @page.name, :rev => @revision_number + 1},
{:class => 'navlink', :accesskey => 'F', :id => 'to_next_revision', :rel => 'nofollow'}) +
" <span class='revisions'>(#{@revision.page.rev_ids.size - @revision_number} more)</span> ".html_safe
else
link_to('Forward in time', {:web => @web.address, :action => 'show', :id => @page.name},
{:class => 'navlink', :accesskey => 'F', :id => 'to_next_revision', :rel => 'nofollow'}) +
" <span class='revisions'>(to current)</span>".html_safe
end
end
def back_for_revision
link_to('Back in time',
{:web => @web.address, :action => 'revision', :id => @page.name, :rev => @revision_number - 1},
{:class => 'navlink', :id => 'to_previous_revision', :rel => 'nofollow'}) +
" <span class='revisions'>(#{@revision_number - 1} more)</span>".html_safe
end
def back_for_page
link_to('Back in time',
{:web => @web.address, :action => 'revision', :id => @page.name,
:rev => @page.rev_ids.size - 1},
{:class => 'navlink', :accesskey => 'B', :id => 'to_previous_revision', :rel => 'nofollow'}) +
" <span class='revisions'>(#{@page.rev_ids.size - 1} #{@page.rev_ids.size - 1 == 1 ? 'revision' : 'revisions'})</span>".html_safe
end
def current_revision
link_to('See current', {:web => @web.address, :action => 'show', :id => @page.name},
{:class => 'navlink', :id => 'to_current_revision'})
end
def see_or_hide_changes_for_revision
link_to(@show_diff ? 'Hide changes' : 'See changes',
{:web => @web.address, :action => 'revision', :id => @page.name, :rev => @revision_number,
:mode => (@show_diff ? nil : 'diff') },
{:class => 'navlink', :accesskey => 'C', :id => 'see_changes', :rel => 'nofollow'})
end
def see_or_hide_changes_for_page
link_to(@show_diff ? 'Hide changes' : 'See changes',
{:web => @web.address, :action => 'show', :id => @page.name, :mode => (@show_diff ? nil : 'diff') },
{:class => 'navlink', :accesskey => 'C', :id => 'see_changes', :rel => 'nofollow'})
end
def rollback
link_to('Rollback',
{:web => @web.address, :action => 'rollback', :id => @page.name, :rev => @revision_number},
{:class => 'navlink', :id => 'rollback', :rel => 'nofollow'})
end
end

View file

@ -1,75 +0,0 @@
# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)
require 'instiki_stringsupport'
class Itex
def self.call(env)
if env["PATH_INFO"] =~ /^\/itex/
[200, {"Content-Type" => "application/xml"}, [response(env)]]
else
[404, {"Content-Type" => "text/html"}, ["Not Found"]]
end
end
private
# plugable XML parser; falls back to REXML
begin
require 'nokogiri'
def self.xmlparse(text)
Nokogiri::XML(text) { |config| config.strict }
end
rescue LoadError
require 'rexml/document'
def self.xmlparse(text)
REXML::Document.new(text)
end
end
#error message to return
def self.error(str)
"<math xmlns='http://www.w3.org/1998/Math/MathML' display='inline'><merror><mtext>" +
str + "</mtext></merror></math>"
end
# itex2MML parser
begin
require 'itextomml'
def self.parse_itex(tex, filter)
Itex2MML::Parser.new.send(filter, tex).to_utf8
end
rescue LoadError
def self.parse_itex(tex, filter)
error("Please install the itex2MML Ruby bindings.")
end
end
# the actual response
def self.response(env)
params = Rack::Request.new(env).params
tex = (params['tex'] || '').purify.strip
case params['display']
when 'block'
filter = :block_filter
else
filter = :inline_filter
end
return "<math xmlns='http://www.w3.org/1998/Math/MathML' display='" +
filter.to_s[/(.*?)_filter/] + "'/>" if tex == ''
begin
doc = parse_itex(tex, filter)
# make sure the result is well-formed, before sending it off
begin
xmlparse(doc)
rescue
return error("Ill-formed XML.")
end
return doc
rescue Itex2MML::Error => e
error(e.to_s)
rescue
error("Unknown Error")
end
end
end

View file

@ -1,18 +1,4 @@
class Author < String class Author < String
attr_accessor :ip attr_accessor :ip
attr_reader :name def initialize(name, ip) @ip = ip; super(name) end
def initialize(name, ip = nil)
@ip = ip
super(name.as_utf8)
end
def name=(value)
self.gsub!(/.+/, value)
end
alias_method :name, :to_s
def <=>(other)
name <=> other.to_s
end
end end

View file

@ -0,0 +1,33 @@
require 'chunks/chunk'
# The category chunk looks for "category: news" on a line by
# itself and parses the terms after the ':' as categories.
# Other classes can search for Category chunks within
# rendered content to find out what categories this page
# should be in.
#
# Category lines can be hidden using ':category: news', for example
class Category < Chunk::Abstract
CATEGORY_PATTERN = /^(:)?category\s*:(.*)$/i
def self.pattern() CATEGORY_PATTERN end
attr_reader :hidden, :list
def initialize(match_data, content)
super(match_data, content)
@hidden = match_data[1]
@list = match_data[2].split(',').map { |c| c.strip }
@unmask_text = ''
if @hidden
@unmask_text = ''
else
category_urls = @list.map { |category| url(category) }.join(', ')
@unmask_text = '<div class="property"> category: ' + category_urls + '</div>'
end
end
# TODO move presentation of page metadata to controller/view
def url(category)
%{<a class="category_link" href="../list/?category=#{category}">#{category}</a>}
end
end

View file

@ -0,0 +1,86 @@
require 'uri/common'
# A chunk is a pattern of text that can be protected
# and interrogated by a renderer. Each Chunk class has a
# +pattern+ that states what sort of text it matches.
# Chunks are initalized by passing in the result of a
# match by its pattern.
module Chunk
class Abstract
# automatically construct the array of derivatives of Chunk::Abstract
@derivatives = []
class << self
attr_reader :derivatives
end
def self::inherited( klass )
Abstract::derivatives << klass
end
# the class name part of the mask strings
def self.mask_string
self.to_s.delete(':').downcase
end
# a regexp that matches all chunk_types masks
def Abstract::mask_re(chunk_types)
tmp = chunk_types.map{|klass| klass.mask_string}.join("|")
Regexp.new("chunk([0-9a-f]+n\\d+)(#{tmp})chunk")
end
attr_reader :text, :unmask_text, :unmask_mode
def initialize(match_data, content)
@text = match_data[0]
@content = content
@unmask_mode = :normal
end
# Find all the chunks of the given type in content
# Each time the pattern is matched, create a new
# chunk for it, and replace the occurance of the chunk
# in this content with its mask.
def self.apply_to(content)
content.gsub!( self.pattern ) do |match|
new_chunk = self.new($~, content)
content.add_chunk(new_chunk)
new_chunk.mask
end
end
# should contain only [a-z0-9]
def mask
@mask ||="chunk#{@id}#{self.class.mask_string}chunk"
end
# We should not use object_id because object_id is not guarantied
# to be unique when we restart the wiki (new object ids can equal old ones
# that were restored from madeleine storage)
def id
@id ||= "#{@content.page_id}n#{@content.chunk_id}"
end
def unmask
@content.sub!(mask, @unmask_text)
end
def rendered?
@unmask_mode == :normal
end
def escaped?
@unmask_mode == :escape
end
def revert
@content.sub!(mask, @text)
# unregister
@content.delete_chunk(self)
end
end
end

View file

@ -0,0 +1,61 @@
$: << File.dirname(__FILE__) + "../../lib"
require 'redcloth'
require 'bluecloth_tweaked'
require 'rdocsupport'
require 'chunks/chunk'
# The markup engines are Chunks that call the one of RedCloth
# or RDoc to convert text. This markup occurs when the chunk is required
# to mask itself.
module Engines
class AbstractEngine
# Convert content to HTML
def self.apply_to(content)
engine = self.new(content)
content.replace(engine.to_html)
end
private
# Never create engines by constructor - use apply_to instead
def initialize(content)
@content = content
end
end
class Textile < AbstractEngine
def to_html
redcloth = RedCloth.new(@content, [:hard_breaks] + @content.options[:engine_opts])
redcloth.filter_html = false
redcloth.no_span_caps = false
redcloth.to_html(:textile)
end
end
class Markdown < AbstractEngine
def to_html
BlueCloth.new(@content, @content.options[:engine_opts]).to_html
end
end
class Mixed < AbstractEngine
def to_html
redcloth = RedCloth.new(@content, @content.options[:engine_opts])
redcloth.filter_html = false
redcloth.no_span_caps = false
redcloth.to_html
end
end
class RDoc < AbstractEngine
def to_html
RDocSupport::RDocFormatter.new(@content).to_html
end
end
MAP = { :textile => Textile, :markdown => Markdown, :mixed => Mixed, :rdoc => RDoc }
MAP.default = Textile
end

View file

@ -0,0 +1,41 @@
require 'chunks/wiki'
# Includes the contents of another page for rendering.
# The include command looks like this: "[[!include PageName]]".
# It is a WikiReference since it refers to another page (PageName)
# and the wiki content using this command must be notified
# of changes to that page.
# If the included page could not be found, a warning is displayed.
class Include < WikiChunk::WikiReference
INCLUDE_PATTERN = /\[\[!include\s+(.*?)\]\]\s*/i
def self.pattern() INCLUDE_PATTERN end
def initialize(match_data, content)
super
@page_name = match_data[1].strip
@unmask_text = get_unmask_text_avoiding_recursion_loops
end
private
def get_unmask_text_avoiding_recursion_loops
if refpage then
refpage.clear_display_cache
if refpage.name == @content.page_name or refpage.wiki_includes.include?(@content.page_name)
# this will break the recursion
@content.delete_chunk(self)
return "<em>Recursive include detected; #{@page_name} --> #{@content.page_name} " +
"--> #{@page_name}</em>\n"
else
@content.merge_chunks(refpage.display_content)
return refpage.display_content.pre_rendered
end
else
return "<em>Could not include #{@page_name}</em>\n"
end
end
end

View file

@ -0,0 +1,31 @@
require 'chunks/chunk'
# These are basic chunks that have a pattern and can be protected.
# They are used by rendering process to prevent wiki rendering
# occuring within literal areas such as <code> and <pre> blocks
# and within HTML tags.
module Literal
class AbstractLiteral < Chunk::Abstract
def initialize(match_data, content)
super
@unmask_text = @text
end
end
# A literal chunk that protects 'code' and 'pre' tags from wiki rendering.
class Pre < AbstractLiteral
PRE_BLOCKS = "a|pre|code"
PRE_PATTERN = Regexp.new('<('+PRE_BLOCKS+')\b[^>]*?>.*?</\1>', Regexp::MULTILINE)
def self.pattern() PRE_PATTERN end
end
# A literal chunk that protects HTML tags from wiki rendering.
class Tags < AbstractLiteral
TAGS = "a|img|em|strong|div|span|table|td|th|ul|ol|li|dl|dt|dd"
TAGS_PATTERN = Regexp.new('<(?:'+TAGS+')[^>]*?>', Regexp::MULTILINE)
def self.pattern() TAGS_PATTERN end
end
end

View file

@ -1,5 +1,4 @@
require 'chunks/chunk' require 'chunks/chunk'
require 'sanitizer'
# This chunks allows certain parts of a wiki page to be hidden from the # This chunks allows certain parts of a wiki page to be hidden from the
# rest of the rendering pipeline. It should be run at the beginning # rest of the rendering pipeline. It should be run at the beginning
@ -14,11 +13,8 @@ require 'sanitizer'
# #
# Author: Mark Reid <mark at threewordslong dot com> # Author: Mark Reid <mark at threewordslong dot com>
# Created: 8th June 2004 # Created: 8th June 2004
class NoWiki < Chunk::Abstract class NoWiki < Chunk::Abstract
include Sanitizer
NOWIKI_PATTERN = Regexp.new('<nowiki>(.*?)</nowiki>', Regexp::MULTILINE) NOWIKI_PATTERN = Regexp.new('<nowiki>(.*?)</nowiki>', Regexp::MULTILINE)
def self.pattern() NOWIKI_PATTERN end def self.pattern() NOWIKI_PATTERN end
@ -26,7 +22,7 @@ class NoWiki < Chunk::Abstract
def initialize(match_data, content) def initialize(match_data, content)
super super
@plain_text = @unmask_text = safe_xhtml_sanitize(match_data[1]) @plain_text = @unmask_text = match_data[1]
end end
end end

18
app/models/chunks/test.rb Normal file
View file

@ -0,0 +1,18 @@
require 'test/unit'
class ChunkTest < Test::Unit::TestCase
# Asserts a number of tests for the given type and text.
def match(type, test_text, expected)
pattern = type.pattern
assert_match(pattern, test_text)
pattern =~ test_text # Previous assertion guarantees match
chunk = type.new($~)
# Test if requested parts are correct.
for method_sym, value in expected do
assert_respond_to(chunk, method_sym)
assert_equal(value, chunk.method(method_sym).call, "Checking value of '#{method_sym}'")
end
end
end

182
app/models/chunks/uri.rb Normal file
View file

@ -0,0 +1,182 @@
require 'chunks/chunk'
# This wiki chunk matches arbitrary URIs, using patterns from the Ruby URI modules.
# It parses out a variety of fields that could be used by renderers to format
# the links in various ways (shortening domain names, hiding email addresses)
# It matches email addresses and host.com.au domains without schemes (http://)
# but adds these on as required.
#
# The heuristic used to match a URI is designed to err on the side of caution.
# That is, it is more likely to not autolink a URI than it is to accidently
# autolink something that is not a URI. The reason behind this is it is easier
# to force a URI link by prefixing 'http://' to it than it is to escape and
# incorrectly marked up non-URI.
#
# I'm using a part of the [ISO 3166-1 Standard][iso3166] for country name suffixes.
# The generic names are from www.bnoack.com/data/countrycode2.html)
# [iso3166]: http://geotags.com/iso3166/
class URIChunk < Chunk::Abstract
include URI::REGEXP::PATTERN
# this condition is to get rid of pesky warnings in tests
unless defined? URIChunk::INTERNET_URI_REGEXP
GENERIC = 'aero|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org'
COUNTRY = 'ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|az|ba|bb|bd|be|' +
'bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cf|cd|cg|ch|ci|ck|cl|' +
'cm|cn|co|cr|cs|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|fi|' +
'fj|fk|fm|fo|fr|fx|ga|gb|gd|ge|gf|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|' +
'hk|hm|hn|hr|ht|hu|id|ie|il|in|io|iq|ir|is|it|jm|jo|jp|ke|kg|kh|ki|km|kn|' +
'kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mk|ml|mm|' +
'mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nt|' +
'nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|' +
'sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|' +
'tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|' +
'ws|ye|yt|yu|za|zm|zr|zw'
# These are needed otherwise HOST will match almost anything
TLDS = "(?:#{GENERIC}|#{COUNTRY})"
# Redefine USERINFO so that it must have non-zero length
USERINFO = "(?:[#{UNRESERVED};:&=+$,]|#{ESCAPED})+"
# unreserved_no_ending = alphanum | mark, but URI_ENDING [)!] excluded
UNRESERVED_NO_ENDING = "-_.~*'(#{ALNUM}"
# this ensures that query or fragment do not end with URI_ENDING
# and enable us to use a much simpler self.pattern Regexp
# uric_no_ending = reserved | unreserved_no_ending | escaped
URIC_NO_ENDING = "(?:[#{UNRESERVED_NO_ENDING}#{RESERVED}]|#{ESCAPED})"
# query = *uric
QUERY = "#{URIC_NO_ENDING}*"
# fragment = *uric
FRAGMENT = "#{URIC_NO_ENDING}*"
# DOMLABEL is defined in the ruby uri library, TLDS is defined above
INTERNET_HOSTNAME = "(?:#{DOMLABEL}\\.)+#{TLDS}"
# Correct a typo bug in ruby 1.8.x lib/uri/common.rb
PORT = '\\d*'
INTERNET_URI =
"(?:(#{SCHEME}):/{0,2})?" + # Optional scheme: (\1)
"(?:(#{USERINFO})@)?" + # Optional userinfo@ (\2)
"(#{INTERNET_HOSTNAME})" + # Mandatory hostname (\3)
"(?::(#{PORT}))?" + # Optional :port (\4)
"(#{ABS_PATH})?" + # Optional absolute path (\5)
"(?:\\?(#{QUERY}))?" + # Optional ?query (\6)
"(?:\\#(#{FRAGMENT}))?" + # Optional #fragment (\7)
'(?=\.?(?:\s|\)|\z))' # ends only with optional dot + space or ")"
# or end of the string
SUSPICIOUS_PRECEDING_CHARACTER = '(!|\"\:|\"|\\\'|\]\()?' # any of !, ":, ", ', ](
INTERNET_URI_REGEXP =
Regexp.new(SUSPICIOUS_PRECEDING_CHARACTER + INTERNET_URI, Regexp::EXTENDED, 'N')
end
def URIChunk.pattern
INTERNET_URI_REGEXP
end
attr_reader :user, :host, :port, :path, :query, :fragment, :link_text
def self.apply_to(content)
content.gsub!( self.pattern ) do |matched_text|
chunk = self.new($~, content)
if chunk.avoid_autolinking?
# do not substitute nor register the chunk
matched_text
else
content.add_chunk(chunk)
chunk.mask
end
end
end
def initialize(match_data, content)
super
@link_text = match_data[0]
@suspicious_preceding_character = match_data[1]
@original_scheme, @user, @host, @port, @path, @query, @fragment = match_data[2..-1]
treat_trailing_character
@unmask_text = "<a href=\"#{uri}\">#{link_text}</a>"
end
def avoid_autolinking?
not @suspicious_preceding_character.nil?
end
def treat_trailing_character
# If the last character matched by URI pattern is in ! or ), this may be part of the markup,
# not a URL. We should handle it as such. It is possible to do it by a regexp, but
# much easier to do programmatically
last_char = @link_text[-1..-1]
if last_char == ')' or last_char == '!'
@trailing_punctuation = last_char
@link_text.chop!
[@original_scheme, @user, @host, @port, @path, @query, @fragment].compact.last.chop!
else
@trailing_punctuation = nil
end
end
def scheme
@original_scheme or (@user ? 'mailto' : 'http')
end
def scheme_delimiter
scheme == 'mailto' ? ':' : '://'
end
def user_delimiter
'@' unless @user.nil?
end
def port_delimiter
':' unless @port.nil?
end
def query_delimiter
'?' unless @query.nil?
end
def uri
[scheme, scheme_delimiter, user, user_delimiter, host, port_delimiter, port, path,
query_delimiter, query].compact.join
end
end
# uri with mandatory scheme but less restrictive hostname, like
# http://localhost:2500/blah.html
class LocalURIChunk < URIChunk
unless defined? LocalURIChunk::LOCAL_URI_REGEXP
# hostname can be just a simple word like 'localhost'
ANY_HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?"
# The basic URI expression as a string
# Scheme and hostname are mandatory
LOCAL_URI =
"(?:(#{SCHEME})://)+" + # Mandatory scheme:// (\1)
"(?:(#{USERINFO})@)?" + # Optional userinfo@ (\2)
"(#{ANY_HOSTNAME})" + # Mandatory hostname (\3)
"(?::(#{PORT}))?" + # Optional :port (\4)
"(#{ABS_PATH})?" + # Optional absolute path (\5)
"(?:\\?(#{QUERY}))?" + # Optional ?query (\6)
"(?:\\#(#{FRAGMENT}))?" + # Optional #fragment (\7)
'(?=\.?(?:\s|\)|\z))' # ends only with optional dot + space or ")"
# or end of the string
LOCAL_URI_REGEXP = Regexp.new(SUSPICIOUS_PRECEDING_CHARACTER + LOCAL_URI, Regexp::EXTENDED, 'N')
end
def LocalURIChunk.pattern
LOCAL_URI_REGEXP
end
end

141
app/models/chunks/wiki.rb Normal file
View file

@ -0,0 +1,141 @@
require 'wiki_words'
require 'chunks/chunk'
require 'chunks/wiki'
require 'cgi'
# Contains all the methods for finding and replacing wiki related links.
module WikiChunk
include Chunk
# A wiki reference is the top-level class for anything that refers to
# another wiki page.
class WikiReference < Chunk::Abstract
# Name of the referenced page
attr_reader :page_name
# the referenced page
def refpage
@content.web.pages[@page_name]
end
end
# A wiki link is the top-level class for links that refers to
# another wiki page.
class WikiLink < WikiReference
attr_reader :link_text, :link_type
def initialize(match_data, content)
super
@link_type = :show
end
def self.apply_to(content)
content.gsub!( self.pattern ) do |matched_text|
chunk = self.new($~, content)
if chunk.textile_url?
# do not substitute
matched_text
else
content.add_chunk(chunk)
chunk.mask
end
end
end
# the referenced page
def refpage
@content.web.pages[@page_name]
end
def textile_url?
not @textile_link_suffix.nil?
end
end
# This chunk matches a WikiWord. WikiWords can be escaped
# by prepending a '\'. When this is the case, the +escaped_text+
# method will return the WikiWord instead of the usual +nil+.
# The +page_name+ method returns the matched WikiWord.
class Word < WikiLink
attr_reader :escaped_text
unless defined? WIKI_WORD
WIKI_WORD = Regexp.new('(":)?(\\\\)?(' + WikiWords::WIKI_WORD_PATTERN + ')\b', 0, "utf-8")
end
def self.pattern
WIKI_WORD
end
def initialize(match_data, content)
super
@textile_link_suffix, @escape, @page_name = match_data[1..3]
if @escape
@unmask_mode = :escape
@escaped_text = @page_name
else
@escaped_text = nil
end
@link_text = WikiWords.separate(@page_name)
@unmask_text = (@escaped_text || @content.page_link(@page_name, @link_text, @link_type))
end
end
# This chunk handles [[bracketted wiki words]] and
# [[AliasedWords|aliased wiki words]]. The first part of an
# aliased wiki word must be a WikiWord. If the WikiWord
# is aliased, the +link_text+ field will contain the
# alias, otherwise +link_text+ will contain the entire
# contents within the double brackets.
#
# NOTE: This chunk must be tested before WikiWord since
# a WikiWords can be a substring of a WikiLink.
class Link < WikiLink
unless defined? WIKI_LINK
WIKI_LINK = /(":)?\[\[\s*([^\]\s][^\]]+?)\s*\]\]/
LINK_TYPE_SEPARATION = Regexp.new('^(.+):((file)|(pic))$', 0, 'utf-8')
ALIAS_SEPARATION = Regexp.new('^(.+)\|(.+)$', 0, 'utf-8')
end
def self.pattern() WIKI_LINK end
def initialize(match_data, content)
super
@textile_link_suffix, @page_name = match_data[1..2]
@link_text = @page_name
separate_link_type
separate_alias
@unmask_text = @content.page_link(@page_name, @link_text, @link_type)
end
private
# if link wihin the brackets has a form of [[filename:file]] or [[filename:pic]],
# this means a link to a picture or a file
def separate_link_type
link_type_match = LINK_TYPE_SEPARATION.match(@page_name)
if link_type_match
@link_text = @page_name = link_type_match[1]
@link_type = link_type_match[2..3].compact[0].to_sym
end
end
# link text may be different from page name. this will look like [[actual page|link text]]
def separate_alias
alias_match = ALIAS_SEPARATION.match(@page_name)
if alias_match
@page_name, @link_text = alias_match[1..2]
end
# note that [[filename|link text:file]] is also supported
end
end
end

58
app/models/file_yard.rb Normal file
View file

@ -0,0 +1,58 @@
require 'fileutils'
require 'instiki_errors'
class FileYard
attr_reader :files_path
def initialize(files_path, max_upload_size)
@files_path, @max_upload_size = files_path, max_upload_size
FileUtils.mkdir_p(@files_path) unless File.exist?(@files_path)
@files = Dir["#{@files_path}/*"].collect{|path| File.basename(path) if File.file?(path) }.compact
end
def upload_file(name, io)
sanitize_file_name(name)
if io.kind_of?(Tempfile)
io.close
check_upload_size(io.size)
File.chmod(600, file_path(name)) if File.exists? file_path(name)
FileUtils.mv(io.path, file_path(name))
else
content = io.read
check_upload_size(content.length)
File.open(file_path(name), 'wb') { |f| f.write(content) }
end
# just in case, restrict read access and prohibit write access to the uploaded file
FileUtils.chmod(0440, file_path(name))
end
def files
Dir["#{files_path}/*"].collect{|path| File.basename(path) if File.file?(path)}.compact
end
def has_file?(name)
files.include?(name)
end
def file_path(name)
"#{files_path}/#{name}"
end
SANE_FILE_NAME = /[a-zA-Z0-9\-_\. ]{1,255}/
def sanitize_file_name(name)
unless name =~ SANE_FILE_NAME or name == '.' or name == '..'
raise Instiki::ValidationError.new("Invalid file name: '#{name}'.\n" +
"Only latin characters, digits, dots, underscores, dashes and spaces are accepted.")
end
end
def check_upload_size(actual_upload_size)
if actual_upload_size > @max_upload_size.kilobytes
raise Instiki::ValidationError.new("Uploaded file size (#{actual_upload_size / 1024} " +
"kbytes) exceeds the maximum (#{@max_upload_size} kbytes) set for this wiki")
end
end
end

View file

@ -1,136 +1,120 @@
class Page < ActiveRecord::Base require 'date'
belongs_to :web require 'page_lock'
has_many :revisions, :order => 'id', :dependent => :destroy require 'revision'
#In many cases, we don't need to instantiate the full revisions (with all that textual data) require 'wiki_words'
has_many :rev_ids, :order => 'id', :class_name => 'Revision', :select => 'id, revised_at, page_id, author, ip' require 'chunks/wiki'
has_many :wiki_references, :order => 'referenced_name'
has_one :current_revision, :class_name => 'Revision', :order => 'id DESC'
def name class Page
read_attribute(:name).as_utf8 include PageLock
attr_reader :name, :web
attr_accessor :revisions
def initialize(web, name)
raise 'nil web' if web.nil?
raise 'nil name' if name.nil?
@web, @name, @revisions = web, name, []
end end
def revise(content, name, time, author, renderer) def revise(content, created_at, author)
revisions_size = new_record? ? 0 : rev_ids.size
if (revisions_size > 0) and content == current_revision.content and name == self.name if not @revisions.empty? and content == @revisions.last.content
raise Instiki::ValidationError.new( raise Instiki::ValidationError.new(
"You have tried to save page '#{name}' without changing its content") "You have tried to save page '#{name}' without changing its content")
end end
self.name = name
author = Author.new(author.to_s) unless author.is_a?(Author)
# Try to render content to make sure that markup engine can take it, # Try to render content to make sure that markup engine can take it,
renderer.revision = Revision.new( # before addin a revision to the page
:page => self, :content => content, :author => author, :revised_at => time) Revision.new(self, @revisions.length, content, created_at, author).force_rendering
renderer.display_content(update_references = true)
# A user may change a page, look at it and make some more changes - several times. # A user may change a page, look at it and make some more changes - several times.
# Not to record every such iteration as a new revision, if the previous revision was done # Not to record every such iteration as a new revision, if the previous revision was done
# by the same author, not more than 30 minutes ago, then update the last revision instead of # by the same author, not more than 30 minutes ago, then update the last revision instead of
# creating a new one # creating a new one
if (revisions_size > 0) && continous_revision?(time, author) if !@revisions.empty? && continous_revision?(created_at, author)
current_revision.update_attributes(:content => content, :revised_at => time) @revisions.last.created_at = created_at
@revisions.last.content = content
@revisions.last.clear_display_cache
else else
revisions.build(:content => content, :author => author, :revised_at => time) @revisions << Revision.new(self, @revisions.length, content, created_at, author)
end
save
self
end end
def rollback(revision_number, time, author_ip, renderer) self.revisions.last.force_rendering
roll_back_revision = self.revisions[revision_number] # at this point the page may not be inserted in the web yet, and therefore
if roll_back_revision.nil? # references to the page itself are rendered as "unresolved". Clearing the cache allows
raise Instiki::ValidationError.new("Revision #{revision_number} not found") # the page to re-render itself once again, hopefully _after_ it is inserted in the web
self.revisions.last.clear_display_cache
@web.refresh_pages_with_references(@name) if @revisions.length == 1
self
end end
author = Author.new(roll_back_revision.author.name, author_ip)
revise(roll_back_revision.content, self.name, time, author, renderer) def rollback(revision_number, created_at, author_ip = nil)
roll_back_revision = @revisions[revision_number].dup
revise(roll_back_revision.content, created_at, Author.new(roll_back_revision.author, author_ip))
end end
def revisions? def revisions?
rev_ids.size > 1 revisions.length > 1
end end
def previous_revision(revision) def revised_on
revision_index = rev_ids.each_with_index do |rev, index| created_on
if rev.id == revision.id
break index
else
nil
end
end
if revision_index.nil? or revision_index == 0
nil
else
revisions[revision_index - 1]
end
end end
def references def in_category?(cat)
web.select.pages_that_reference(name) cat.nil? || cat.empty? || categories.include?(cat)
end
def wiki_words
wiki_references.select { |ref| ref.wiki_word? }.map { |ref| ref.referenced_name }
end end
def categories def categories
wiki_references.select { |ref| ref.category? }.map { |ref| ref.referenced_name } display_content.find_chunks(Category).map { |cat| cat.list }.flatten
end
def authors
revisions.collect { |rev| rev.author }
end
def references
@web.select.pages_that_reference(name)
end end
def linked_from def linked_from
web.select.pages_that_link_to(name) @web.select.pages_that_link_to(name)
end
def redirects
wiki_references.select { |ref| ref.redirected_page? }.map { |ref| ref.referenced_name }
end end
def included_from def included_from
web.select.pages_that_include(name) @web.select.pages_that_include(name)
end end
# Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page". # Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page".
def plain_name def plain_name
web.brackets_only? ? name.escapeHTML.html_safe : WikiWords.separate(name).escapeHTML.html_safe @web.brackets_only ? name : WikiWords.separate(name)
end end
LOCKING_PERIOD = 30.minutes # used to build chunk ids.
def id
def lock(time, locked_by) @id ||= name.unpack('H*').first
update_attributes(:locked_at => time, :locked_by => locked_by)
end end
def lock_duration(time) def link(options = {})
((time - locked_at) / 60).to_i unless locked_at.nil? @web.make_link(name, nil, options)
end end
def unlock def author_link(options = {})
update_attribute(:locked_at, nil) @web.make_link(author, nil, options)
end
def locked?(comparison_time)
locked_at + LOCKING_PERIOD > comparison_time unless locked_at.nil?
end
def to_param
name.as_utf8
end end
private private
def continous_revision?(time, author) def continous_revision?(created_at, author)
(current_revision.author == author) && (revised_at + 30.minutes > time) @revisions.last.author == author && @revisions.last.created_at + 30.minutes > created_at
end end
# Forward method calls to the current revision, so the page responds to all revision calls # Forward method calls to the current revision, so the page responds to all revision calls
def method_missing(method_id, *args, &block) def method_missing(method_symbol)
method_name = method_id.to_s revisions.last.send(method_symbol)
# Perform a hand-off to AR::Base#method_missing
if @attributes.include?(method_name) or md = /(=|\?|_before_type_cast)$/.match(method_name)
super(method_id, *args, &block)
else
current_revision.send(method_id)
end
end end
end end

23
app/models/page_lock.rb Normal file
View file

@ -0,0 +1,23 @@
# Contains all the lock methods to be mixed in with the page
module PageLock
LOCKING_PERIOD = 30 * 60 # 30 minutes
attr_reader :locked_by
def lock(time, locked_by)
@locked_at, @locked_by = time, locked_by
end
def lock_duration(time)
((time - @locked_at) / 60).to_i unless @locked_at.nil?
end
def unlock
@locked_at = nil
end
def locked?(comparison_time)
@locked_at + LOCKING_PERIOD > comparison_time unless @locked_at.nil?
end
end

View file

@ -1,15 +0,0 @@
# This class maintains the state of wiki references for newly created or newly deleted pages
class PageObserver < ActiveRecord::Observer
def after_create(page)
WikiReference.update_all("link_type = '#{WikiReference::LINKED_PAGE}'",
['referenced_name = ?', page.name])
end
def before_destroy(page)
WikiReference.delete_all ['page_id = ?', page.id]
WikiReference.update_all("link_type = '#{WikiReference::WANTED_PAGE}'",
['referenced_name = ?', page.name])
end
end

View file

@ -7,7 +7,7 @@ class PageSet < Array
@web = web @web = web
# if pages is not specified, make a list of all pages in the web # if pages is not specified, make a list of all pages in the web
if pages.nil? if pages.nil?
super(web.pages) super(web.pages.values)
# otherwise use specified pages and condition to produce a set of pages # otherwise use specified pages and condition to produce a set of pages
elsif condition.nil? elsif condition.nil?
super(pages) super(pages)
@ -17,9 +17,10 @@ class PageSet < Array
end end
def most_recent_revision def most_recent_revision
self.map { |page| page.revised_at }.max || Time.at(0) self.map { |page| page.created_at }.max || Time.at(0)
end end
def by_name def by_name
PageSet.new(@web, sort_by { |page| page.name }) PageSet.new(@web, sort_by { |page| page.name })
end end
@ -27,28 +28,22 @@ class PageSet < Array
alias :sort :by_name alias :sort :by_name
def by_revision def by_revision
PageSet.new(@web, sort_by { |page| page.revised_at }).reverse PageSet.new(@web, sort_by { |page| page.created_at }).reverse
end end
def pages_that_reference(page_name) def pages_that_reference(page_name)
all_referring_pages = WikiReference.pages_that_reference(@web, page_name) self.select { |page| page.wiki_references.include?(page_name) }
self.select { |page| all_referring_pages.include?(page.name) }
end end
def pages_that_link_to(page_name) def pages_that_link_to(page_name)
all_linking_pages = WikiReference.pages_that_link_to(@web, page_name) self.select { |page| page.wiki_words.include?(page_name) }
self.select { |page| all_linking_pages.include?(page.name) }
end end
def pages_that_include(page_name) def pages_that_include(page_name)
all_including_pages = WikiReference.pages_that_include(@web, page_name) self.select { |page| page.wiki_includes.include?(page_name) }
self.select { |page| all_including_pages.include?(page.name) }
end end
def pages_authored_by(author) def pages_authored_by(author)
all_pages_authored_by_the_author =
Page.connection.select_all(sanitize_sql([
"SELECT page_id FROM revision WHERE author = '?'", author]))
self.select { |page| page.authors.include?(author) } self.select { |page| page.authors.include?(author) }
end end
@ -62,43 +57,33 @@ class PageSet < Array
# references and so cannot be orphans # references and so cannot be orphans
# Pages that refer to themselves and have no links from outside are oprphans. # Pages that refer to themselves and have no links from outside are oprphans.
def orphaned_pages def orphaned_pages
never_orphans = web.authors + ['HomePage'] never_orphans = web.select.authors + ['HomePage']
self.select { |page| self.select { |page|
if never_orphans.include? page.name if never_orphans.include? page.name
false false
else else
references = (WikiReference.pages_that_reference(@web, page.name) + references = pages_that_reference(page.name)
WikiReference.pages_redirected_to(@web, page.name)).uniq references.empty? or references == [page]
references.empty? or references == [page.name]
end end
} }
end end
def pages_in_category(category)
self.select { |page|
WikiReference.pages_in_category(web, category).map.include?(page.name)
}
end
# Returns all the wiki words in this page set for which # Returns all the wiki words in this page set for which
# there are no pages in this page set's web # there are no pages in this page set's web
def wanted_pages def wanted_pages
known_pages = (web.select.names + redirected_names).uniq wiki_words - web.select.names
wiki_words - known_pages
end end
def names def names
self.map { |page| page.name } self.map { |page| page.name }
end end
def redirected_names
self.wiki_words.select {|name| web.has_redirect_for?(name) }.uniq.sort
end
def wiki_words def wiki_words
self.inject([]) { |wiki_words, page| self.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
wiki_words + page.wiki_words end
}.flatten.uniq.sort
def authors
self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort
end end
end end

View file

@ -1,8 +1,127 @@
class Revision < ActiveRecord::Base require 'diff'
belongs_to :page require 'wiki_content'
composed_of :author, :mapping => [ %w(author name), %w(ip ip) ] require 'chunks/wiki'
require 'date'
require 'author'
require 'page'
def content class Revision
read_attribute(:content).as_utf8
attr_accessor :page, :number, :content, :created_at, :author
def initialize(page, number, content, created_at, author)
@page, @number, @created_at, @author = page, number, created_at, author
self.content = content
@display_cache = nil
end
def created_on
Date.new(@created_at.year, @created_at.mon, @created_at.day)
end
def pretty_created_at
# Must use DateTime because Time doesn't support %e on at least some platforms
DateTime.new(
@created_at.year, @created_at.mon, @created_at.day, @created_at.hour, @created_at.min
).strftime "%B %e, %Y %H:%M"
end
# todo: drop next_revision, previuous_revision and number from here - unused code
def next_revision
page.revisions[number + 1]
end
def previous_revision
number > 0 ? page.revisions[number - 1] : nil
end
# Returns an array of all the WikiIncludes present in the content of this revision.
def wiki_includes
unless @wiki_includes_cache
chunks = display_content.find_chunks(Include)
@wiki_includes_cache = chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq
end
@wiki_includes_cache
end
# Returns an array of all the WikiReferences present in the content of this revision.
def wiki_references
unless @wiki_references_cache
chunks = display_content.find_chunks(WikiChunk::WikiReference)
@wiki_references_cache = chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq
end
@wiki_references_cache
end
# Returns an array of all the WikiWords present in the content of this revision.
def wiki_words
unless @wiki_words_cache
wiki_chunks = display_content.find_chunks(WikiChunk::WikiLink)
@wiki_words_cache = wiki_chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq
end
@wiki_words_cache
end
# Returns an array of all the WikiWords present in the content of this revision.
# that already exists as a page in the web.
def existing_pages
wiki_words.select { |wiki_word| page.web.pages[wiki_word] }
end
# Returns an array of all the WikiWords present in the content of this revision
# that *doesn't* already exists as a page in the web.
def unexisting_pages
wiki_words - existing_pages
end
# Explicit check for new type of display cache with chunks_by_type method.
# Ensures new version works with older snapshots.
def display_content
unless @display_cache && @display_cache.respond_to?(:chunks_by_type)
@display_cache = WikiContent.new(self)
@display_cache.render!
end
@display_cache
end
def display_diff
previous_revision ? HTMLDiff.diff(previous_revision.display_content, display_content) : display_content
end
def clear_display_cache
@wiki_words_cache = @published_cache = @display_cache = @wiki_includes_cache =
@wiki_references_cache = nil
end
def display_published
unless @published_cache && @published_cache.respond_to?(:chunks_by_type)
@published_cache = WikiContent.new(self, {:mode => :publish})
@published_cache.render!
end
@published_cache
end
def display_content_for_export
WikiContent.new(self, {:mode => :export} ).render!
end
def force_rendering
begin
display_content.render!
rescue => e
ApplicationController.logger.error "Failed rendering page #{@name}"
ApplicationController.logger.error e
message = e.message
# substitute content with an error message
self.content = <<-EOL
<p>Markup engine has failed to render this page, raising the following error:</p>
<p>#{message}</p>
<pre>#{self.content}</pre>
EOL
clear_display_cache
raise e
end end
end end
end

View file

@ -1,4 +0,0 @@
class System < ActiveRecord::Base
set_table_name 'system'
validates_presence_of :password
end

View file

@ -1,221 +1,133 @@
require 'instiki_stringsupport' require 'cgi'
require 'page'
require 'page_set'
require 'wiki_words'
require 'zip/zip'
class Web < ActiveRecord::Base class Web
## Associations attr_accessor :name, :password, :safe_mode, :pages
attr_accessor :additional_style, :allow_uploads, :published
attr_reader :address
has_many :pages, :dependent => :destroy # there are getters for all these attributes, too
has_many :wiki_files, :dependent => :destroy attr_writer :markup, :color, :brackets_only, :count_pages, :max_upload_size
has_many :revisions, :through => :pages def initialize(parent_wiki, name, address, password = nil)
self.address = address
@wiki, @name, @password = parent_wiki, name, password
## Hooks set_compatible_defaults
before_save :sanitize_markup @pages = {}
after_save :create_files_directory @allow_uploads = true
@additional_style = nil
before_validation :validate_address @published = false
@count_pages = false
## Validations
validates_uniqueness_of :address, :message => 'already exists'
validates_length_of :color, :in => 3..6
## Methods
# @return [Wiki] a new Wiki instance
def wiki
Wiki.new
end end
def settings_changed?(markup, safe_mode, brackets_only) # Explicitly sets value of some web attributes to defaults, unless they are already set
self.markup != markup || def set_compatible_defaults
self.safe_mode != safe_mode || @markup = markup()
self.brackets_only != brackets_only @color = color()
@safe_mode = safe_mode()
@brackets_only = brackets_only()
@max_upload_size = max_upload_size()
@wiki = wiki
end end
def add_page(name, content, time, author, renderer) # All below getters know their default values. This is necessary to ensure compatibility with
page = page(name) || pages.build(:name => name) # 0.9 storages, where they were not defined.
page.revise(content, name, time, author, renderer) def brackets_only() @brackets_only || false end
def color() @color ||= '008B26' end
def count_pages() @count_pages || false end
def markup() @markup ||= :textile end
def max_upload_size() @max_upload_size || 100; end
def wiki() @wiki ||= WikiService.instance; end
def add_page(name, content, created_at, author)
page = Page.new(self, name)
page.revise(content, created_at, author)
@pages[page.name] = page
end
def address=(the_address)
if the_address != CGI.escape(the_address)
raise Instiki::ValidationError.new('Web name should contain only valid URI characters')
end
@address = the_address
end end
# @return [Array<String>] a collection of all the names of the authors that
# have ever contributed to the pages for this Web
def authors def authors
revisions.all( select.authors
:select => "DISTINCT revisions.author",
:order => "1"
).collect(&:author)
end end
def categories def categories
select.map { |page| page.categories }.flatten.uniq.sort select.map { |page| page.categories }.flatten.uniq.sort
end end
# @param [String] name the name of some associated Page record to find
# @return [Page, nil] the associated Page record, or +nil+ if no record is
# found with the provided name
def page(name)
pages.find_by_name(name)
end
# @return [Page] the last associated Page record
def last_page
pages.last
end
# @param [String] name the name of some potential Page record
# @return [Boolean] whether or not a given Page record exists with a given
# name
def has_page?(name) def has_page?(name)
pages.exists?(:name => name) pages[name]
end end
def has_redirect_for?(name) def has_file?(name)
WikiReference.page_that_redirects_for(self, name) wiki.file_yard(self).has_file?(name)
end end
def page_that_redirects_for(name) # Create a link for the given page name and link text based
page(WikiReference.page_that_redirects_for(self, name)) # on the render mode in options and whether the page exists
end # in the this web.
# The links a relative, and will work only if displayed on another WikiPage.
def has_file?(file_name) # It should not be used in menus, templates and such - instead, use link_to_page helper
wiki_files.exists?(:file_name => file_name) def make_link(name, text = nil, options = {})
end text = CGI.escapeHTML(text || WikiWords.separate(name))
mode = options[:mode] || :show
def file_list(sort_order="file_name") base_url = options[:base_url] || '..'
wiki_files.all(:order => sort_order) link_type = options[:link_type] || :show
end case link_type.to_sym
when :show
def pages_that_link_to(page_name) UrlGenerator.new.make_page_link(mode, name, text, base_url, has_page?(name))
WikiReference.pages_that_link_to(self, page_name) when :file
end UrlGenerator.new.make_file_link(mode, name, text, base_url, has_file?(name))
when :pic
def pages_that_link_to_file(file_name) UrlGenerator.new.make_pic_link(mode, name, text, base_url, has_file?(name))
WikiReference.pages_that_link_to_file(self, file_name) else
end raise "Unknown link type: #{link_type}"
# @param [String] file_name the name of some WikiFile of interest
# @return [String, nil] the description of some WikiFile of interest, nil if
# the WikiFile could not be found
def description(file_name)
wiki_files.find_by_file_name(file_name).try(:description)
end
# @return [Symbol] the type of markup used by this Web
def markup
self[:markup].to_sym
end
# @return [Hash] a Hash wherein the key is some author's name, and the
# values are an array of page names for that author.
def page_names_by_author
data = revisions.all(
:select => "DISTINCT revisions.author AS author, pages.name AS page_name",
:order => "pages.name"
)
data.inject({}) do |result, revision|
result[revision.author] ||= []
result[revision.author] << revision.page_name
result
end end
end end
# OPTIMIZE Use the +delete_all+ with conditions for extra efficiency # Clears the display cache for all the pages with references to
def refresh_pages_with_references(page_name)
select.pages_that_reference(page_name).each { |page|
page.revisions.each { |revision| revision.clear_display_cache }
}
end
def refresh_revisions
select.each { |page| page.revisions.each { |revision| revision.clear_display_cache } }
end
def remove_pages(pages_to_be_removed) def remove_pages(pages_to_be_removed)
pages_to_be_removed.each { |p| p.destroy } pages.delete_if { |page_name, page| pages_to_be_removed.include?(page) }
end end
def revised_at def revised_on
select.most_recent_revision select.most_recent_revision
end end
def select(&condition) def select(&condition)
PageSet.new(self, pages, condition) PageSet.new(self, @pages.values, condition)
end
def select_all
PageSet.new(self, pages, nil)
end
# @return [String] uses the +address+ attribute for this record's parameter name
def to_param
address
end
# Called by an +after_save+ hook. Creates the directory that houses this
# Web's associated files.
#
# TODO Move this into the WikiFile model
def create_files_directory
return unless allow_uploads == 1
dummy_file = wiki_files.build(
:file_name => "0",
:description => "0",
:content => "0"
)
File.umask(0002)
begin
dummy_file.content_path.parent.mkpath
dummy_file.save
dummy_file.destroy
rescue => e
logger.error "Failed create files directory for #{address}: #{e}"
raise "Instiki could not create directory to store uploaded files. " +
"Please make sure that Instiki is allowed to create directory " +
"#{dummy_file.content_path.expand_path} and add files to it."
end
end
# @return [Pathname] the path to the files for this record
def files_path
path = Rails.root.join("webs")
if default_web?
path.join("files")
else
path.join(address, "files")
end
end
# @return [Pathname] the path to PNGs for this record
def blahtex_pngs_path
files_path.join("pngs")
end end
private private
# Returns an array of all the wiki words in any current revision # Returns an array of all the wiki words in any current revision
def wiki_words def wiki_words
pages.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq pages.values.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
end end
# Returns an array of all the page names on this web # Returns an array of all the page names on this web
def page_names def page_names
pages.map { |p| p.name } pages.keys
end end
protected
def sanitize_markup
self.markup = markup.to_s
end
def validate_address
if ['create_system', 'create_web', 'delete_web', 'delete_files', 'web_list', ''].include?(address)
self.errors.add(:address, 'is not a valid address')
raise Instiki::ValidationError.new("\"#{address.purify.escapeHTML}\" #{errors.on(:address)}")
end
unless address == CGI.escape(address)
self.errors.add(:address, 'should contain only valid URI characters')
raise Instiki::ValidationError.new("#{self.class.human_attribute_name('address')} #{errors.on(:address)}")
end
end
# @return [Boolean] whether or not this record is considered the default Web
def default_web?
defined?(DEFAULT_WEB) && address == DEFAULT_WEB
end
end end

View file

@ -1,111 +0,0 @@
class Wiki
cattr_accessor :storage_path, :logger
self.storage_path = Rails.root.join('storage')
def authenticate(password)
password == (system.password || 'instiki')
end
def create_web(name, address, password = nil)
@webs = nil
Web.create(:name => name, :address => address, :password => password)
end
def delete_web(address)
web = Web.find_by_address(address)
unless web.nil?
web.destroy
@webs = nil
end
end
def edit_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false,
password = nil, published = false, brackets_only = false, count_pages = false,
allow_uploads = true, max_upload_size = nil)
if not (web = Web.find_by_address(old_address))
raise Instiki::ValidationError.new("Web with address '#{old_address}' does not exist")
end
old_files_path = web.files_path
web.update_attributes(:address => new_address, :name => name, :markup => markup, :color => color,
:additional_style => additional_style, :safe_mode => safe_mode, :password => password, :published => published,
:brackets_only => brackets_only, :count_pages => count_pages, :allow_uploads => allow_uploads, :max_upload_size => max_upload_size)
@webs = nil
raise Instiki::ValidationError.new("There is already a web with address '#{new_address}'") unless web.errors.on(:address).nil?
web
move_files(old_files_path, web.files_path)
end
def move_files(old_path, new_path)
return if new_path == old_path
default_path = Rails.root.join("webs", "files")
FileUtils.rmdir(new_path) if File.exist?(new_path)
if [old_path, new_path].include? default_path
File.rename(old_path, new_path)
FileUtils.rmdir(old_path.parent) unless old_path == default_path
else
File.rename(old_path.parent, new_path.parent)
end
end
def read_page(web_address, page_name)
ApplicationController.logger.debug "Reading page '#{page_name}' from web '#{web_address}'"
web = Web.find_by_address(web_address)
if web.nil?
ApplicationController.logger.debug "Web '#{web_address}' not found"
return nil
else
page = web.pages.first(:conditions => ['name = ?', page_name])
ApplicationController.logger.debug "Page '#{page_name}' #{page.nil? ? 'not' : ''} found"
return page
end
end
def remove_orphaned_pages(web_address)
web = Web.find_by_address(web_address)
web.remove_pages(web.select.orphaned_pages)
end
def remove_orphaned_pages_in_category(web_address,category)
web = Web.find_by_address(web_address)
pages_in_category = PageSet.new(web, web.select.pages_in_category(category))
web.remove_pages(pages_in_category.orphaned_pages)
end
def revise_page(web_address, page_name, new_name, content, revised_at, author, renderer)
page = read_page(web_address, page_name)
page.revise(content, new_name, revised_at, author, renderer)
end
def rollback_page(web_address, page_name, revision_number, time, author_id = nil)
page = read_page(web_address, page_name)
page.rollback(revision_number, time, author_id)
end
def setup(password, web_name, web_address)
system.update_attribute(:password, password)
create_web(web_name, web_address)
end
def system
@system ||= (System.first() || System.create)
end
def setup?
Web.count > 0
end
def webs
@webs ||= Web.all.inject({}) { |webs, web| webs.merge(web.address => web) }
end
def storage_path
self.class.storage_path
end
def write_page(web_address, page_name, content, written_on, author, renderer)
Web.find_by_address(web_address).add_page(page_name, content, written_on, author, renderer)
end
end

209
app/models/wiki_content.rb Normal file
View file

@ -0,0 +1,209 @@
require 'cgi'
require 'chunks/engines'
require 'chunks/category'
require 'chunks/include'
require 'chunks/wiki'
require 'chunks/literal'
require 'chunks/uri'
require 'chunks/nowiki'
# Wiki content is just a string that can process itself with a chain of
# actions. The actions can modify wiki content so that certain parts of
# it are protected from being rendered by later actions.
#
# When wiki content is rendered, it can be interrogated to find out
# which chunks were rendered. This means things like categories, wiki
# links, can be determined.
#
# Exactly how wiki content is rendered is determined by a number of
# settings that are optionally passed in to a constructor. The current
# options are:
# * :engine
# => The structural markup engine to use (Textile, Markdown, RDoc)
# * :engine_opts
# => A list of options to pass to the markup engines (safe modes, etc)
# * :pre_engine_actions
# => A list of render actions or chunks to be processed before the
# markup engine is applied. By default this is:
# Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word
# * :post_engine_actions
# => A list of render actions or chunks to apply after the markup
# engine. By default these are:
# Literal::Pre, Literal::Tags
# * :mode
# => How should the content be rendered? For normal display (show),
# publishing (:publish) or export (:export)?
module ChunkManager
attr_reader :chunks_by_type, :chunks_by_id, :chunks, :chunk_id
ACTIVE_CHUNKS = [ NoWiki, Category, WikiChunk::Link, URIChunk, LocalURIChunk,
WikiChunk::Word ]
HIDE_CHUNKS = [ Literal::Pre, Literal::Tags ]
MASK_RE = {
ACTIVE_CHUNKS => Chunk::Abstract.mask_re(ACTIVE_CHUNKS),
HIDE_CHUNKS => Chunk::Abstract.mask_re(HIDE_CHUNKS)
}
def init_chunk_manager
@chunks_by_type = Hash.new
Chunk::Abstract::derivatives.each{|chunk_type|
@chunks_by_type[chunk_type] = Array.new
}
@chunks_by_id = Hash.new
@chunks = []
@chunk_id = 0
end
def add_chunk(c)
@chunks_by_type[c.class] << c
@chunks_by_id[c.id] = c
@chunks << c
@chunk_id += 1
end
def delete_chunk(c)
@chunks_by_type[c.class].delete(c)
@chunks_by_id.delete(c.id)
@chunks.delete(c)
end
def merge_chunks(other)
other.chunks.each{|c| add_chunk(c)}
end
def scan_chunkid(text)
text.scan(MASK_RE[ACTIVE_CHUNKS]){|a| yield a[0] }
end
def find_chunks(chunk_type)
@chunks.select { |chunk| chunk.kind_of?(chunk_type) and chunk.rendered? }
end
# for testing and WikiContentStub; we need a page_id even if we have no page
def page_id
0
end
end
# A simplified version of WikiContent. Useful to avoid recursion problems in
# WikiContent.new
class WikiContentStub < String
attr_reader :options
include ChunkManager
def initialize(content, options)
super(content)
@options = options
init_chunk_manager
end
# Detects the mask strings contained in the text of chunks of type chunk_types
# and yields the corresponding chunk ids
# example: content = "chunk123categorychunk <pre>chunk456categorychunk</pre>"
# inside_chunks([Literal::Pre]) ==> yield 456
def inside_chunks(chunk_types)
chunk_types.each { |chunk_type| chunk_type.apply_to(self) }
chunk_types.each { |chunk_type| @chunks_by_type[chunk_type].each { |chunk|
scan_chunkid(chunk.text) { |id|
yield id
}
}
}
end
end
class WikiContent < String
include ChunkManager
DEFAULT_OPTS = {
:active_chunks => ACTIVE_CHUNKS,
:engine => Engines::Textile,
:engine_opts => [],
:mode => :show
}.freeze
attr_reader :web, :options, :revision, :not_rendered, :pre_rendered
# Create a new wiki content string from the given one.
# The options are explained at the top of this file.
def initialize(revision, options = {})
@revision = revision
@web = @revision.page.web
@options = DEFAULT_OPTS.dup.merge(options)
@options[:engine] = Engines::MAP[@web.markup]
@options[:engine_opts] = [:filter_html, :filter_styles] if @web.safe_mode
@options[:active_chunks] -= [WikiChunk::Word] if @web.brackets_only
@not_rendered = @pre_rendered = nil
super(@revision.content)
init_chunk_manager
build_chunks
@not_rendered = String.new(self)
end
# Call @web.page_link using current options.
def page_link(name, text, link_type)
@options[:link_type] = (link_type || :show)
@web.make_link(name, text, @options)
end
def build_chunks
# create and mask Includes and "active_chunks" chunks
Include.apply_to(self)
@options[:active_chunks].each{|chunk_type| chunk_type.apply_to(self)}
# Handle hiding contexts like "pre" and "code" etc..
# The markup (textile, rdoc etc) can produce such contexts with its own syntax.
# To reveal them, we work on a copy of the content.
# The copy is rendered and used to detect the chunks that are inside protecting context
# These chunks are reverted on the original content string.
copy = WikiContentStub.new(self, @options)
@options[:engine].apply_to(copy)
copy.inside_chunks(HIDE_CHUNKS) do |id|
# Some markup engines can replicate parts of content while converting to HTML
# Hence the if in the below line
@chunks_by_id[id].revert if @chunks_by_id.key?(id)
end
end
def pre_render!
unless @pre_rendered
@chunks_by_type[Include].each{|chunk| chunk.unmask }
@pre_rendered = String.new(self)
end
@pre_rendered
end
def render!
pre_render!
@options[:engine].apply_to(self)
# unmask in one go. $~[1] is the chunk id
gsub!(MASK_RE[ACTIVE_CHUNKS]){
if chunk = @chunks_by_id[$~[1]]
chunk.unmask_text
# if we match a chunkmask that existed in the original content string
# just keep it as it is
else
$~[0]
end}
self
end
def page_name
@revision.page.name
end
def page_id
@revision.page.id
end
end

View file

@ -1,66 +0,0 @@
class WikiFile < ActiveRecord::Base
belongs_to :web
before_save :write_content_to_file
before_destroy :delete_content_file
validates_presence_of %w( web file_name )
validates_length_of :file_name, :within=>1..50
validates_length_of :description, :maximum=>255
def self.find_by_file_name(file_name)
first(:conditions => ['file_name = ?', file_name])
end
def validate
if file_name
if ! WikiFile.is_valid?(file_name)
errors.add("file_name", "is invalid. Only latin characters, digits, dots, underscores, " +
"dashes and spaces are accepted")
elsif file_name == '.' or file_name == '..'
errors.add("file_name", "cannot be '.' or '..'")
end
end
if @web and @content
if (@content.size > @web.max_upload_size.kilobytes)
errors.add("content", "size (#{(@content.size / 1024.0).round} kilobytes) exceeds " +
"the maximum (#{web.max_upload_size} kilobytes) set for this wiki")
end
end
errors.add("content", "is empty") if @content.nil? or @content.empty?
end
def content=(content)
if content.respond_to? :read
@content = content.read
else
@content = content
end
end
def content
@content ||= ( File.open(content_path, 'rb') { |f| f.read } )
end
def content_path
web.files_path.join(file_name)
end
def write_content_to_file
web.create_files_directory unless File.exists?(web.files_path)
File.open(self.content_path, 'wb') { |f| f.write(@content) }
end
def delete_content_file
require 'fileutils'
FileUtils.rm_f(content_path) if File.exists?(content_path)
end
SANE_FILE_NAME = /^[a-zA-Z0-9\-_\. ]*$/
def self.is_valid?(name)
name =~ SANE_FILE_NAME
end
end

View file

@ -1,139 +0,0 @@
class WikiReference < ActiveRecord::Base
LINKED_PAGE = 'L'
WANTED_PAGE = 'W'
REDIRECTED_PAGE = 'R'
INCLUDED_PAGE = 'I'
CATEGORY = 'C'
AUTHOR = 'A'
FILE = 'F'
WANTED_FILE = 'E'
belongs_to :page
validates_inclusion_of :link_type, :in => [LINKED_PAGE, WANTED_PAGE, REDIRECTED_PAGE, INCLUDED_PAGE, CATEGORY, AUTHOR, FILE, WANTED_FILE]
def referenced_name
read_attribute(:referenced_name).as_utf8
end
def self.link_type(web, page_name)
if web.has_page?(page_name) || self.page_that_redirects_for(web, page_name)
LINKED_PAGE
else
WANTED_PAGE
end
end
def self.pages_that_reference(web, page_name)
query = 'SELECT name FROM pages JOIN wiki_references ' +
'ON pages.id = wiki_references.page_id ' +
'WHERE wiki_references.referenced_name = ? ' +
"AND wiki_references.link_type in ('#{LINKED_PAGE}', '#{WANTED_PAGE}', '#{INCLUDED_PAGE}') " +
"AND pages.web_id = '#{web.id}'"
names = connection.select_all(sanitize_sql([query, page_name])).map { |row| row['name'] }
end
def self.pages_that_link_to(web, page_name)
query = 'SELECT name FROM pages JOIN wiki_references ' +
'ON pages.id = wiki_references.page_id ' +
'WHERE wiki_references.referenced_name = ? ' +
"AND wiki_references.link_type in ('#{LINKED_PAGE}','#{WANTED_PAGE}') " +
"AND pages.web_id = '#{web.id}'"
names = connection.select_all(sanitize_sql([query, page_name])).map { |row| row['name'] }
end
def self.pages_that_link_to_file(web, file_name)
query = 'SELECT name FROM pages JOIN wiki_references ' +
'ON pages.id = wiki_references.page_id ' +
'WHERE wiki_references.referenced_name = ? ' +
"AND wiki_references.link_type in ('#{FILE}','#{WANTED_FILE}') " +
"AND pages.web_id = '#{web.id}'"
names = connection.select_all(sanitize_sql([query, file_name])).map { |row| row['name'] }
end
def self.pages_that_include(web, page_name)
query = 'SELECT name FROM pages JOIN wiki_references ' +
'ON pages.id = wiki_references.page_id ' +
'WHERE wiki_references.referenced_name = ? ' +
"AND wiki_references.link_type = '#{INCLUDED_PAGE}' " +
"AND pages.web_id = '#{web.id}'"
names = connection.select_all(sanitize_sql([query, page_name])).map { |row| row['name'] }
end
def self.pages_redirected_to(web, page_name)
names = []
redirected_pages = []
page = web.page(page_name)
redirected_pages.concat page.redirects
redirected_pages.concat Thread.current[:page_redirects][page] if
Thread.current[:page_redirects] && Thread.current[:page_redirects][page]
redirected_pages.uniq.each { |name| names.concat self.pages_that_reference(web, name) }
names.uniq
end
def self.page_that_redirects_for(web, page_name)
query = 'SELECT name FROM pages JOIN wiki_references ' +
'ON pages.id = wiki_references.page_id ' +
'WHERE wiki_references.referenced_name = ? ' +
"AND wiki_references.link_type = '#{REDIRECTED_PAGE}' " +
"AND pages.web_id = '#{web.id}'"
row = connection.select_one(sanitize_sql([query, page_name]))
row['name'].as_utf8 if row
end
def self.pages_in_category(web, category)
query =
"SELECT name FROM pages JOIN wiki_references " +
"ON pages.id = wiki_references.page_id " +
"WHERE wiki_references.referenced_name = ? " +
"AND wiki_references.link_type = '#{CATEGORY}' " +
"AND pages.web_id = '#{web.id}'"
names = connection.select_all(sanitize_sql([query, category])).map { |row| row['name'].as_utf8 }
end
def self.list_categories(web)
query = "SELECT DISTINCT wiki_references.referenced_name " +
"FROM wiki_references LEFT OUTER JOIN pages " +
"ON wiki_references.page_id = pages.id " +
"WHERE wiki_references.link_type = '#{CATEGORY}' " +
"AND pages.web_id = '#{web.id}'"
connection.select_all(query).map { |row| row['referenced_name'].as_utf8 }
end
def wiki_word?
linked_page? or wanted_page?
end
def wiki_link?
linked_page? or wanted_page? or file? or wanted_file?
end
def linked_page?
link_type == LINKED_PAGE
end
def redirected_page?
link_type == REDIRECTED_PAGE
end
def wanted_page?
link_type == WANTED_PAGE
end
def included_page?
link_type == INCLUDED_PAGE
end
def file?
link_type == FILE
end
def wanted_file?
link_type == WANTED_FILE
end
def category?
link_type == CATEGORY
end
end

259
app/models/wiki_service.rb Normal file
View file

@ -0,0 +1,259 @@
require 'open-uri'
require 'yaml'
require 'madeleine'
require 'madeleine/automatic'
require 'madeleine/zmarshal'
require 'web'
require 'page'
require 'author'
require 'file_yard'
require 'instiki_errors'
module AbstractWikiService
attr_reader :webs, :system
def authenticate(password)
# system['password'] variant is for compatibility with storages from older versions
password == (@system[:password] || @system['password'] || 'instiki')
end
def create_web(name, address, password = nil)
@webs[address] = Web.new(self, name, address, password) unless @webs[address]
end
def delete_web(address)
@webs[address] = nil
end
def file_yard(web)
raise "Web #{@web.name} does not belong to this wiki service" unless @webs.values.include?(web)
# TODO cache FileYards
FileYard.new("#{self.storage_path}/#{web.address}", web.max_upload_size)
end
def init_wiki_service
@webs = {}
@system = {}
end
def edit_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false,
password = nil, published = false, brackets_only = false, count_pages = false,
allow_uploads = true, max_upload_size = nil)
if not @webs.key? old_address
raise Instiki::ValidationError.new("Web with address '#{old_address}' does not exist")
end
if old_address != new_address
if @webs.key? new_address
raise Instiki::ValidationError.new("There is already a web with address '#{new_address}'")
end
@webs[new_address] = @webs[old_address]
@webs.delete(old_address)
@webs[new_address].address = new_address
end
web = @webs[new_address]
web.refresh_revisions if settings_changed?(web, markup, safe_mode, brackets_only)
web.name, web.markup, web.color, web.additional_style, web.safe_mode =
name, markup, color, additional_style, safe_mode
web.password, web.published, web.brackets_only, web.count_pages =
password, published, brackets_only, count_pages, allow_uploads
web.allow_uploads, web.max_upload_size = allow_uploads, max_upload_size.to_i
end
def read_page(web_address, page_name)
ApplicationController.logger.debug "Reading page '#{page_name}' from web '#{web_address}'"
web = @webs[web_address]
if web.nil?
ApplicationController.logger.debug "Web '#{web_address}' not found"
return nil
else
page = web.pages[page_name]
ApplicationController.logger.debug "Page '#{page_name}' #{page.nil? ? 'not' : ''} found"
return page
end
end
def remove_orphaned_pages(web_address)
@webs[web_address].remove_pages(@webs[web_address].select.orphaned_pages)
end
def revise_page(web_address, page_name, content, revised_on, author)
page = read_page(web_address, page_name)
page.revise(content, revised_on, author)
end
def rollback_page(web_address, page_name, revision_number, created_at, author_id = nil)
page = read_page(web_address, page_name)
page.rollback(revision_number, created_at, author_id)
end
def setup(password, web_name, web_address)
@system[:password] = password
create_web(web_name, web_address)
end
def setup?
not (@webs.empty?)
end
def storage_path
self.class.storage_path
end
def write_page(web_address, page_name, content, written_on, author)
@webs[web_address].add_page(page_name, content, written_on, author)
end
private
def settings_changed?(web, markup, safe_mode, brackets_only)
web.markup != markup ||
web.safe_mode != safe_mode ||
web.brackets_only != brackets_only
end
end
class WikiService
include AbstractWikiService
include Madeleine::Automatic::Interceptor
# These methods do not change the state of persistent objects, and
# should not be logged by Madeleine
automatic_read_only :authenticate, :read_page, :setup?, :webs, :storage_path, :file_yard
@@storage_path = './storage/'
class << self
def check_snapshot_thread
# @madeleine may not be initialised in unit tests, and in such case there is no need to do anything
@madeleine.check_snapshot_thread unless @madeleine.nil?
end
def clean_storage
MadeleineServer.clean_storage(self)
end
# One interesting property of Madeleine as persistence mechanism is that it saves
# (and restores) the whole ObjectSpace. And in there, storage from older version may contain
# who knows what in temporary variables, such as caches of various kinds.
# The reason why it is nearly impossible to control is that there may be bugs, people may
# use modified versions of things, etc etc etc
# Therefore, upon loading the storage from a file, it is a good idea to clear all such
# variables. It would be better yet if Madeleine could be somehow instructed not to save that
# data in a snapshot at all. Alas, such a feature is not presently available.
def clear_all_caches
return if @system.webs.nil?
@system.webs.each_value do |web|
next if web.nil? or web.pages.nil?
web.pages.each_value do |page|
next if page.nil? or page.revisions.nil?
page.revisions.each { |revision| revision.clear_display_cache }
end
end
end
def instance
@madeleine ||= MadeleineServer.new(self)
@system = @madeleine.system
clear_all_caches
return @system
end
def snapshot
@madeleine.snapshot
end
def storage_path=(storage_path)
@@storage_path = storage_path
end
def storage_path
@@storage_path
end
end
def initialize
init_wiki_service
end
end
class MadeleineServer
attr_reader :storage_path
# Clears all the command_log and snapshot files located in the storage directory, so the
# database is essentially dropped and recreated as blank
def self.clean_storage(service)
begin
Dir.foreach(service.storage_path) do |file|
if file =~ /(command_log|snapshot)$/
File.delete(File.join(service.storage_path, file))
end
end
rescue
Dir.mkdir(service.storage_path)
end
end
def initialize(service)
@storage_path = service.storage_path
@server = Madeleine::Automatic::AutomaticSnapshotMadeleine.new(service.storage_path,
Madeleine::ZMarshal.new) {
service.new
}
@snapshoot_thread_running = false
end
def command_log_present?
not Dir[storage_path + '/*.command_log'].empty?
end
def snapshot
@server.take_snapshot
end
def check_snapshot_thread
start_snapshot_thread unless @snapshoot_thread_running
end
def start_snapshot_thread
@snapshoot_thread_running = true
Thread.new(@server) {
hours_since_last_snapshot = 0
while true
begin
hours_since_last_snapshot += 1
# Take a snapshot if there is a command log, or 24 hours
# have passed since the last snapshot
if command_log_present? or hours_since_last_snapshot >= 24
ActionController::Base.logger.info "[#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}] " +
'Taking a Madeleine snapshot'
snapshot
hours_since_last_snapshot = 0
end
sleep(1.hour)
rescue => e
ActionController::Base.logger.error(e)
# wait for a minute (not to spoof the log with the same error)
# and go back into the loop, to keep trying
sleep(1.minute)
ActionController::Base.logger.info("Retrying to save a snapshot")
end
end
}
end
def system
@server.system
end
end

23
app/models/wiki_words.rb Normal file
View file

@ -0,0 +1,23 @@
# Contains all the methods for finding and replacing wiki words
module WikiWords
# In order of appearance: Latin, greek, cyrillian, armenian
I18N_HIGHER_CASE_LETTERS =
"À<EFBFBD>?ÂÃÄÅĀĄĂÆÇĆČĈĊĎ<C48A>?ÈÉÊËĒĘĚĔĖĜĞĠĢĤĦÌ<C4A6><>?ĪĨĬĮİIJĴĶ<C4B4>?ĽĹĻĿÑŃŇŅŊÒÓÔÕÖØŌ<C398>?ŎŒŔŘŖŚŠŞŜȘŤŢŦȚÙÚÛÜŪŮŰŬŨŲŴ<C5B2>?ŶŸŹŽŻ" +
"ΑΒΓΔΕΖΗΘΙΚΛΜ<EFBFBD>?ΞΟΠΡΣΤΥΦΧΨΩ" +
"ΆΈΉΊΌΎ<EFBFBD>?ѠѢѤѦѨѪѬѮѰѲѴѶѸѺѼѾҀҊҌҎ<D28C>?ҒҔҖҘҚҜҞҠҢҤҦҨҪҬҮҰҲҴҶҸҺҼҾ<D2BC>?ӃӅӇӉӋ<D389>?<3F>?ӒӔӖӘӚӜӞӠӢӤӦӨӪӬӮӰӲӴӸЖ" +
"ԱԲԳԴԵԶԷԸԹԺԻԼԽԾԿՀ<EFBFBD>?ՂՃՄՅՆՇՈՉՊՋՌ<D58B>?<3F>?<3F>?ՑՒՓՔՕՖ"
I18N_LOWER_CASE_LETTERS =
"àáâãäå<EFBFBD>?ąăæçć<C3A7>?ĉċ<C489>?đèéêëēęěĕėƒ<C497>?ğġģĥħìíîïīĩĭįıijĵķĸłľĺļŀñńňņʼnŋòóôõöø<C3B6><>?œŕřŗśšş<C5A1>?șťţŧțùúûüūůűŭũųŵýÿŷžżźÞþßſ<C39F>" +
"άέήίΰαβγδεζηθικλμνξοπ<EFBFBD>στυφχψωϊϋό<CF8B><>?" +
"абвгдежзийклмнопр<EFBFBD>уфхцчшщъыь<D18B><>?<3F>?ёђѓєѕіїјљћќ<D19B>?ўџѡѣѥѧѩѫѭѯѱѳѵѷѹѻѽѿ<D1BD><>?<3F>?ґғҕҗҙқ<D299>?ҟҡңҥҧҩҫҭүұҳҵҷҹһҽҿӀӂӄӆӈӊӌӎӑӓӕӗәӛ<D399>?ӟӡӣӥӧөӫӭӯӱӳӵӹ" +
"աբգդեզէըթժիլխծկհձղճմյնշոչպջռսվտր<EFBFBD>?ւփքօֆև"
WIKI_WORD_PATTERN = '[A-Z' + I18N_HIGHER_CASE_LETTERS + '][a-z' + I18N_LOWER_CASE_LETTERS + ']+[A-Z' + I18N_HIGHER_CASE_LETTERS + ']\w+'
CAMEL_CASED_WORD_BORDER = /([a-z#{I18N_LOWER_CASE_LETTERS}])([A-Z#{I18N_HIGHER_CASE_LETTERS}])/u
def self.separate(wiki_word)
wiki_word.gsub(CAMEL_CASED_WORD_BORDER, '\1 \2')
end
end

View file

@ -1,4 +1,4 @@
<%- @title = "Instiki Setup"; @content_width = 500 -%> <% @title = "Instiki Setup"; @content_width = 500 %>
<p> <p>
Congratulations on succesfully installing and starting Instiki. Congratulations on succesfully installing and starting Instiki.
@ -6,9 +6,8 @@
you'll need to do a brief one-time setup. you'll need to do a brief one-time setup.
</p> </p>
<% form_tag({ :controller => 'admin', :action => 'create_system' }, <%= form_tag({ :controller => 'admin', :action => 'create_system'},
{ 'id' => 'setup', 'method' => 'post', 'onsubmit' => 'return validateSetup()', {'id' => 'setup', 'method' => 'post', 'onSubmit' => 'return validateSetup()'})
'accept-charset' => 'utf-8' }) do
%> %>
<ol class="setup"> <ol class="setup">
<li> <li>
@ -21,10 +20,10 @@
The address can only consist of letters and digits. The address can only consist of letters and digits.
</div> </div>
<div class="inputBox"> <div class="inputBox">
<label for="web_name">Name:</label> <input type="text" id="web_name" name="web_name" value="Wiki" Name: <input type="text" id="web_name" name="web_name" value="Wiki"
onchange="proposeAddress();" onclick="this.value == 'Wiki' ? this.value = '' : true" /> onChange="proposeAddress();" onClick="this.value == 'Wiki' ? this.value = '' : true" />
&#xa0;&#xa0; &nbsp;&nbsp;
<label for="web_address">Address:</label> <input type="text" id="web_address" name="web_address" onchange="cleanAddress();" Address: <input type="text" id="web_address" name="web_address" onChange="cleanAddress();"
value="wiki" /> value="wiki" />
</div> </div>
</li> </li>
@ -32,23 +31,23 @@
<li> <li>
<h2 style="margin-bottom: 3px">Password for creating and changing webs</h2> <h2 style="margin-bottom: 3px">Password for creating and changing webs</h2>
<div class="help"> <div class="help">
Administrative access allows you to make new webs and change existing ones. Administrative access allows you to make new webs and change existing ones.<br/>
Everyone with this password will be able to do this, so pick it carefully.
</div> </div>
<div class="help"><em>Everyone with this password will be able to do this, so pick it carefully!</em></div>
<div class="inputBox"> <div class="inputBox">
<label for="password">Password:</label> <input type="password" id="password" name="password" /> Password: <input type="password" id="password" name="password" />
&#xa0;&#xa0; &nbsp;&nbsp;
<label for="password_check">Verify:</label> <input type="password" id="password_check" name="password_check" /> Verify: <input type="password" id="password_check" name="password_check" />
</div> </div>
</li> </li>
</ol> </ol>
<p style="text-align:right"> <p align="right">
<input type="submit" value="Setup" style="margin-left: 40px" /> <input type="submit" value="Setup" style="margin-left: 40px" />
</p> </p>
<% end %> <%= end_form_tag %>
<script type="text/javascript"> <script>
function proposeAddress() { function proposeAddress() {
document.getElementById('web_address').value = document.getElementById('web_address').value =
document.getElementById('web_name').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase(); document.getElementById('web_name').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();

View file

@ -1,14 +1,12 @@
<%- @title = "New Wiki Web"; @content_width = 500 -%> <% @title = "New Wiki Web"; @content_width = 500 %>
<p> <p>
Each web serves as an isolated name space for wiki pages, Each web serves as an isolated name space for wiki pages,
so different subjects or projects can write about different <i>MuppetShows</i>. so different subjects or projects can write about different <i>MuppetShows</i>.
</p> </p>
<% form_tag({ :controller => 'admin', :action => 'create_web' }, <%= form_tag({ :controller => 'admin', :action => 'create_web'},
{ 'id' => 'setup', 'method' => 'post', {'id' => 'setup', 'method' => 'post', 'onSubmit' => 'cleanAddress(); return validateSetup()'})
'onsubmit' => 'cleanAddress(); return validateSetup()',
'accept-charset' => 'utf-8' }) do
%> %>
<ol class="setup"> <ol class="setup">
@ -21,24 +19,26 @@
The address can only consist of letters and digits. The address can only consist of letters and digits.
</div> </div>
<div class="inputBox"> <div class="inputBox">
<label for="web_name">Name:</label> <input type="text" id="web_name" name="name" onchange="proposeAddress();" /> Name: <input type="text" id="web_name" name="name" onChange="proposeAddress();" />
&#xa0;&#xa0; &nbsp;&nbsp;
<label for="web_address">Address:</label> <input type="text" id="web_address" name="address" onchange="cleanAddress();" /> Address: <input type="text" id="web_address" name="address" onChange="cleanAddress();" />
</div> </div>
</li> </li>
</ol> </ol>
<p style="text-align:right;font-size:.85em;"> <p align="right">
<label for="system_password">Enter system password</label> <small>
Enter system password
<input type="password" id="system_password" name="system_password" /> <input type="password" id="system_password" name="system_password" />
and and
<input type="submit" value="Create Web" /> <input type="submit" value="Create Web" />
</small>
</p> </p>
<%- end -%> <%= end_form_tag %>
<script type="text/javascript"> <script>
function proposeAddress() { function proposeAddress() {
document.getElementById('web_address').value = document.getElementById('web_address').value =
document.getElementById('web_name').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase(); document.getElementById('web_name').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();

View file

@ -1,9 +1,7 @@
<%- @title = "Edit Web" -%> <% @title = "Edit Web" %>
<% form_tag({ :controller => 'admin', :action => 'edit_web', :web => @web.address }, <%= form_tag({ :controller => 'admin', :action => 'edit_web', :web => @web.address },
{ 'id' => 'setup', 'method' => 'post', {'id' => 'setup', 'method' => 'post', 'onSubmit' => 'cleanAddress(); return validateSetup()'})
'onsubmit' => 'cleanAddress(); return validateSetup()',
'accept-charset' => 'utf-8' }) do
%> %>
<h2 style="margin-bottom: 3px">Name and address</h2> <h2 style="margin-bottom: 3px">Name and address</h2>
@ -13,70 +11,71 @@
Ex: the address "rails" gives URLs like <i>/rails/show/HomePage</i>. Ex: the address "rails" gives URLs like <i>/rails/show/HomePage</i>.
</div> </div>
<div class="inputBox"> <div class="inputBox, disableAutoComplete">
<label for ="name">Name:</label> <input type="text" id="name" name="name" class="disableAutoComplete" value="<%= @web.name %>" Name: <input type="text" id="name" name="name" value="<%= @web.name %>"
onchange="proposeAddress();" /> &#xa0;&#xa0; onChange="proposeAddress();" /> &nbsp;&nbsp;
<label for="address">Address:</label> <input type="text" class="disableAutoComplete" id="address" name="address" value="<%= @web.address %>" Address: <input type="text" id="address" name="address" value="<%= @web.address %>"
onchange="cleanAddress();" /> onChange="cleanAddress();" />
<em>(Letters and digits only)</em> <i>(Letters and digits only)</i>
</div> </div>
<h2 style="margin-bottom: 3px">Specialize</h2> <h2 style="margin-bottom: 3px">Specialize</h2>
<div class="inputBox"> <div class="inputBox, disableAutoComplete">
<label for="markup">Markup:</label> Markup:
<select id="markup" name="markup"> <select name="markup">
<%= html_options({'Textile' => :textile, 'Markdown' => :markdown, 'Markdown+itex2MML' => :markdownMML, 'Markdown+blahtex/PNG' => :markdownPNG, <%= html_options({'Textile' => :textile, 'Markdown' => :markdown, 'Mixed' => :mixed,
'Mixed' => :mixed, 'RDoc' => :rdoc }, @web.markup) %> 'RDoc' => :rdoc }, @web.markup) %>
</select> </select>
&#xa0;&#xa0; &nbsp;&nbsp;
<label for="color">Color:</label> Color:
<select id="color" name="color"> <select name="color">
<%= html_options({ <%= html_options({ 'Green' => '008B26', 'Purple' => '504685', 'Red' => 'DA0006',
'Green' => '008B26', 'Orange' => 'FA6F00', 'Grey' => '8BA2B0' }, @web.color) %>
'Purple' => '504685',
'Red' => 'DA0006',
'Orange' => 'C50',
'Grey' => '8BA2B0',
'Blue' => '204A87',
'Brown' => '8F5902',
'Scarlet Red' => 'AA0006',
'Plum' => '5C3566'
}, @web.color) %>
</select> </select>
<br/>
<p> <p>
<input type="checkbox" class="disableAutoComplete" id="safe_mode" name="safe_mode" <%= raw 'checked="checked"' if @web.safe_mode? %> /> <small>
<label for="safe_mode">Safe mode <input type="checkbox" name="safe_mode" <%= 'checked="on"' if @web.safe_mode %> />
<em>- strip HTML tags and stylesheet options from the content of all pages</em></label> Safe mode
<em>- strip HTML tags and stylesheet options from the content of all pages</em>
<br/> <br/>
<input type="checkbox" class="disableAutoComplete" id="brackets_only" name="brackets_only" <%= raw 'checked="checked"' if @web.brackets_only? %> /> <input type="checkbox" name="brackets_only" <%= 'checked="on"' if @web.brackets_only %> />
<label for="brackets_only">Brackets only Brackets only
<em>- require all wiki words to be as [[wiki word]], WikiWord links won't be created</em></label> <em>- require all wiki words to be as [[wiki word]], WikiWord links won't be created</em>
<br/> <br/>
<input type="checkbox" class="disableAutoComplete" id="count_pages" name="count_pages" <%= raw 'checked="checked"' if @web.count_pages? %> /> <input type="checkbox" name="count_pages" <%= 'checked="on"' if @web.count_pages %> />
<label for="count_pages">Count pages</label> Count pages
<br/> <br/>
<input type="checkbox" class="disableAutoComplete" name="allow_uploads" <%= raw 'checked="checked"' if @web.allow_uploads? %> /> <!--
File uploads are not ready for gfeneral use, and therefore are disabled in release 0.10
TODO Enable these input elements again after release 0.10
<input type="checkbox" name="allow_uploads" <%= 'checked="on"' if @web.allow_uploads %> />
Allow uploads of no more than Allow uploads of no more than
<input type="text" class="disableAutoComplete" name="max_upload_size" value="<%= @web.max_upload_size %>" <input type="text" name="max_upload_size" value="<%= @web.max_upload_size %>"
size="20" /> width="20" />
kbytes kbytes
<em>- <em>-
allow users to upload pictures and other files and include them on wiki pages allow users to upload pictures and other files and include them on wiki pages
</em> </em>
<br/>
-->
</small>
</p> </p>
<a href="#" onclick="toggleView('additionalStyle');return false;"> <a href="#"
onClick="document.getElementById('additionalStyle').style.display='block';return false;">
Stylesheet tweaks &gt;&gt;</a> Stylesheet tweaks &gt;&gt;</a>
<em> <small><em>
- add or change styles used by this web; styles defined here take precedence over - add or change styles used by this web; styles defined here take precedence over
instiki.css.<br/> instiki.css. Hint: View HTML source of a page you want to style to find ID names on individual
Hint: View HTML source of a page you want to style to find ID names on individual tags.</em></small>
tags.</em>
<br/> <br/>
<textarea id="additionalStyle" class="disableAutoComplete" cols="50" rows="20" <textarea id="additionalStyle"
style="display:none" name="additional_style"><%= h(@web.additional_style) %> style="display: none; margin-top: 10px; margin-bottom: 5px; width: 560px; height: 200px"
name="additional_style"><%= @web.additional_style %>
</textarea> </textarea>
</div> </div>
@ -86,10 +85,10 @@
Setting the password to nothing will remove the password protection. Setting the password to nothing will remove the password protection.
</div> </div>
<div class="inputBox"> <div class="inputBox">
<label for="password">Password:</label> <input class="disableAutoComplete" type="password" id="password" Password: <input class="disableAutoComplete" type="password" id="password"
name="password" value="<%= @web.password %>" /> name="password" value="<%= @web.password %>" />
&#xa0;&#xa0; &nbsp;&nbsp;
<label for="password_check">Verify:</label> <input class="disableAutoComplete" type="password" id="password_check" Verify: <input class="disableAutoComplete" type="password" id="password_check"
value="<%= @web.password %>" name="password_check" /> value="<%= @web.password %>" name="password_check" />
</div> </div>
@ -97,77 +96,43 @@
<div class="help"> <div class="help">
You can turn on a read-only version of this web that's accessible even when the regular web You can turn on a read-only version of this web that's accessible even when the regular web
is password protected. is password protected.
The published version is accessible through URLs like /<%= @web.address %>/published/HomePage. The published version is accessible through URLs like /wiki/published/HomePage.
</div> </div>
<div class="inputBox"> <div class="inputBox">
<input type="checkbox" id="published" name="published" class="disableAutoComplete" <%= raw 'checked="checked"' if @web.published? %> /> <input type="checkbox" name="published" <%= 'checked="on"' if @web.published %> />
<label for="published">Publish this web</label> Publish this web
</div> </div>
<p style="text-align:right;font-size:.85em;"> <p align="right">
<label for="system_password">Enter system password</label> <small>
Enter system password
<input type="password" class="disableAutoComplete" id="system_password" <input type="password" class="disableAutoComplete" id="system_password"
name="system_password" /> name="system_password" />
and and
<input type="submit" value="Update Web" /> <input type="submit" value="Update Web" />
<br/><br/> <br/><br/>
...or forget changes and <%= link_to 'create a new web', :action => 'create_web' %> ...or forget changes and <%= link_to 'create a new web', :action => 'create_web' %>
</small>
</p> </p>
<%- end %>
<%= end_form_tag %>
<br/>
<h1>Other administrative tasks</h1> <h1>Other administrative tasks</h1>
<% form_tag({:controller => 'admin', :web => @web.address, :action => 'remove_orphaned_pages'}, <%= form_tag({:controller => 'admin', :web => @web.address, :action => 'remove_orphaned_pages'},
{:id => 'remove_orphaned_pages', {:id => 'remove_orphaned_pages',
:onsubmit => "return checkSystemPassword(document.getElementById('system_password_orphaned').value)", :onSubmit => "return checkSystemPassword(document.getElementById('system_password_orphaned').value)"
'accept-charset' => 'utf-8' }) do })
%> %>
<div class="inputBox"> <p align="right">
<%= link_to ' Manage uploaded files', <small>
{:controller => 'wiki', :web => @web.address, :action => 'file_list'}, :style => 'font-weight:bold' %> Clean up by entering system password
for this web (<%= @web.name %>) <input type="password" id="system_password_orphaned" name="system_password_orphaned" />
</div>
<p style="text-align:right;font-size:.85em;">
Clean up this web (<%= @web.name %>) by entering the system password
<input type="password" id="system_password_orphaned" class="disableAutoComplete" name="system_password_orphaned" />
and and
<input type="submit" value="Delete Orphan Pages" /> <input type="submit" value="Delete Orphan Pages" />
</small>
</p> </p>
<%- end -%> <%= end_form_tag %>
<%- categories = WikiReference.list_categories(@web).sort
if categories.length > 0 -%>
<% form_tag({:controller => 'admin', :web => @web.address, :action => 'remove_orphaned_pages_in_category'},
{ :id => 'remove_orphaned_pages_in_category',
:onsubmit => "return checkSystemPassword(document.getElementById('system_password_orphaned_in_category').value)",
'accept-charset' => 'utf-8' }) do
%>
<p style="text-align:right;font-size:.85em;">
Clean up selected category:
<select id="category" name="category">
<%= html_options(categories) %>
</select>
Enter system password
<input type="password" id="system_password_orphaned_in_category" class="disableAutoComplete" name="system_password_orphaned_in_category" />
and
<input type="submit" value="Delete Orphan Pages in Category" />
</p>
<%- end -%>
<%- end -%>
<div class="inputBox">
<% form_tag({:controller => 'admin', :web => @web.address, :action => 'delete_web'},
{ :id => 'delete_web',
:onsubmit => "return checkSystemPassword(document.getElementById('system_password_delete_web').value)",
'accept-charset' => 'utf-8' }) do
%>
<p style="text-align:right;">
Delete this web (<%= @web.name %>), and all its pages. Enter system password
<input type="password" id="system_password_delete_web" class="disableAutoComplete" name="system_password_delete_web" />
and
<input type="submit" value="Delete Web" />
</p>
<%- end %>
</div>
<%= javascript_include_tag 'edit_web' %> <%= javascript_include_tag 'edit_web' %>

View file

@ -1,18 +0,0 @@
<%-
@title = "Delete #{@file_name}".html_safe
@hide_navigation = true
-%>
<%= error_messages_for 'delete' %>
<% form_tag({ :controller => 'file', :web => @web_name, :action => 'delete' },
{'accept-charset' => 'utf-8' }) do %>
<div class="inputFieldWithPrompt">
<%= hidden_field 'file', 'file_name' %>
<label for="system_password">Enter system password</label>
<input type="password" class="disableAutoComplete" id="system_password"
name="system_password" />
<input type="submit" value="Delete" /><br/>
or go <%= link_to "back", :back %> to the page you came from.
</div>
<%- end -%>

View file

@ -1,33 +1,19 @@
<%- <%
@title = "Upload #{h @file_name}" @title = "Upload #{@file_name}"
@hide_navigation = false @hide_navigation = false
@referring_page ||= request.env["HTTP_REFERER"] %>
-%>
<%= error_messages_for 'file' %> <p>
<%= form_tag({:controller => 'file', :web => @web_name, :action => 'file'}, {:multipart => true}) %>
<% form_tag({ :controller => 'file', :web => @web_name, :action => 'file' }, <p>
{ 'multipart' => true , 'accept-charset' => 'utf-8' }) do %> File to upload:
<div class="inputFieldWithPrompt">
<input type="hidden" name="referring_page" value="<%= h @referring_page %>" />
<%= hidden_field 'file', 'file_name' %>
<label for="file_content"><b>Content of <%= h @file_name %> to upload</b>:</label>
<br/> <br/>
<input type="file" id="file_content" name="file[content]" size="40" /> <input type="file" name="file" size="40" />
<br /> </p>
Please note that the file you are uploading will be named <%= h @file_name %> on the wiki - <p>
regardless of how it is named on your computer. To change the wiki name of the file, please go <input type="submit" value="Update" /> as
<%= link_to("back", @referring_page) %> and edit the wiki page that refers to the file. <input type="text" name="author" id="authorName" value="<%= @author %>"
</div> onClick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
<div class="inputFieldWithPrompt"> </p>
<label for="file_description"><b>Description</b>:</label> <%= end_form_tag %>
<br/> </p>
<%= text_field "file", "description", "size" => 40 %>
</div>
<div>
<input type="submit" value="Upload" /> as
<%= text_field_tag :author, @author,
:onfocus => "this.value == 'AnonymousCoward' ? this.value = '' : true;",
:onblur => "this.value == '' ? this.value = 'AnonymousCoward' : true" %>
</div>
<%- end -%>

View file

@ -1,5 +1,5 @@
<p> <p>
<% form_tag({}, { 'multipart' => true, 'accept-charset' => 'utf-8' }) do %> <%= form_tag({}, {:multipart => true}) %>
<p> <p>
File to upload: File to upload:
<br/> <br/>
@ -13,11 +13,11 @@
<p> <p>
<input type="submit" value="Update" /> as <input type="submit" value="Update" /> as
<input type="text" name="author" id="authorName" value="<%= @author %>" <input type="text" name="author" id="authorName" value="<%= @author %>"
onclick="this.value == 'AnonymousCoward' ? this.value = '' : true" /> onClick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
<%- if @page -%> <% if @page %>
| <%= link_to 'Cancel', :web => @web.address, :action => 'file'%> <em>(unlocks page)</em> | <%= link_to 'Cancel', :web => @web.address, :action => 'file'%> <small>(unlocks page)</small>
<%- end -%> <% end %>
</p> </p>
<%- end -%> <%= end_form_tag %>
</p> </p>

View file

@ -1,62 +1,42 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg-flat.dtd" > <!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"> <html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<title> <title>
<%- if @page and (@page.name == 'HomePage') and (%w( show published print ).include?(params['action'])) -%> <% if @page and (@page.name == 'HomePage') and (%w( show published print ).include?(@action_name)) %>
<%= h(@web.name) + (@show_diff ? ' (changes)' : '') %> <%= @web.name %>
<%- elsif @web -%> <% elsif @web %>
<%= @title %> in <%= h @web.name %> <%= @title %> in <%= @web.name %>
<%- else -%> <% else %>
<%= @title %> <%= @title %>
<%- end -%> <% end %>
</title> </title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="robots" content="<%= @robots_metatag_value %>" />
<%= javascript_include_tag 'page_helper' %>
<%= stylesheet_link_tag 'instiki', :media => 'all' unless @inline_style %>
<%= stylesheet_link_tag 'syntax', :media => 'all' unless @inline_style %>
<style type="text/css"> <style type="text/css">
h1#pageName, div.info, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover, [actiontype="toggle"]:hover, #TextileHelp h3 { h1#pageName, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover, #TextileHelp h3 {
color: #<%= @web ? @web.color : "393" %>; color: #<%= @web ? @web.color : "393" %>;
} }
a:visited.existingWikiWord {
color: #<%= darken(@web ? @web.color : "393") %>; #Container, #Content {
width: <%= @content_width || "600" %>px;
} }
<%= Rails.root.join('public', 'stylesheets', 'instiki.css').read if @inline_style %> <%= File.read(RAILS_ROOT + '/public/stylesheets/instiki.css') if @inline_style %>
<%= Rails.root.join('public', 'stylesheets', 'syntax.css').read if @inline_style %>
</style> </style>
<%= "<style type='text/css'>#{@style_additions}</style>".html_safe if @style_additions %>
<style type="text/css"><!--/*--><![CDATA[/*><!--*/ <%= stylesheet_link_tag 'instiki' unless @inline_style %>
<%= @web && @web.additional_style ? @web.additional_style.html_safe : '' %>
/*]]>*/--></style> <style type="text/css">
<%= javascript_include_tag :defaults %> <%= @style_additions %>
<%= csrf_meta_tag %> <%= @web ? @web.additional_style : '' %>
<%- if @web -%> </style>
<%- if @web.markup == :markdownMML -%>
<script type="text/x-mathjax-config"> <% if @web %>
MathJax.Hub.Config({ <%= auto_discovery_link_tag(:rss, :controller => 'wiki', :web => @web.address, :action => 'rss_with_headlines') %>
MathML: { useMathMLspacing: true }, <%= auto_discovery_link_tag(:rss, :controller => 'wiki', :web => @web.address, :action => 'rss_with_content') %>
"HTML-CSS": { scale: 90 } <% end %>
});
if (window._onload_fired_) MathJax.Hub.Startup.onload();
</script>
<script type="text/javascript">
if (!(Prototype.Browser.Gecko || navigator.userAgent.match(/MathPlayer/))) {
var s = document.createElement('script');
s.src = "<%= compute_public_path('MathJax.js', 'MathJax').split('?')[0] %>?config=MML_HTMLorMML";
document.querySelector('head').appendChild(s);
window.addEventListener("load", function(){window._onload_fired_ = true} , false);
};
</script>
<%- end -%>
<%= auto_discovery_link_tag(:atom, {:controller => 'wiki', :web => @web.address, :action => 'atom_with_headlines'},
:title => 'Atom with headlines') %>
<%= auto_discovery_link_tag(:atom, {:controller => 'wiki', :web => @web.address, :action => 'atom_with_content'},
:title => 'Atom with full content') %>
<%- end -%>
</head> </head>
<body> <body>
@ -64,35 +44,34 @@
<div id="Container"> <div id="Container">
<div id="Content"> <div id="Content">
<h1 id="pageName"> <h1 id="pageName">
<%= render(:file => 'svg_logo') if xhtml_enabled? %> <% if @page and (@page.name == 'HomePage') and %w( show published print ).include?(@action_name) %>
<%- if @page and (@page.name == 'HomePage') and %w( show published print ).include?(@action_name) -%> <%= @web.name %>
<%= h(@web.name) + (@show_diff ? ' (changes)' : '') %> <% elsif @web %>
<%- elsif @web -%> <small><%= @web.name %></small><br />
<span class="webName"><%= @web.name %></span><br />
<%= @title %> <%= @title %>
<%- else -%> <% else %>
<%= @title %> <%= @title %>
<%- end %> <% end %>
</h1> </h1>
<%= render(:file => 'navigation') unless @web.nil? || @hide_navigation %> <% if @error or @flash[:error] %> <div id="error">
<hr/><p><%= escape_preserving_linefeeds(@error || @flash[:error]) %></p><hr/></div>
<% end %>
<%- if flash[:info] -%> <% if @flash[:info] %> <div id="info">
<div class="info"><%= escape_preserving_linefeeds flash[:info] %></div> <hr/><p><%= escape_preserving_linefeeds @flash[:info] %></p><hr/></div>
<%- end -%> <% end %>
<%- if @error or flash[:error] -%>
<div class="errorExplanation"><%= escape_preserving_linefeeds(@error || flash[:error]) %></div>
<%- end -%>
<%= render 'navigation' unless @web.nil? || @hide_navigation %>
<%= @content_for_layout %> <%= @content_for_layout %>
<%- if @show_footer -%> <% if @show_footer %>
<div id="footer"> <div id="footer">
<div>This site is running on <a href="http://golem.ph.utexas.edu/instiki/show/HomePage">Instiki <%= "#{Instiki::VERSION::STRING}" %></a></div> <p>This site is running on <a href="http://instiki.org/">Instiki</a></p>
<div>Powered by <a href="http://rubyonrails.com/">Ruby on Rails</a> <%= "#{Rails::VERSION::STRING}" %></div> <br/>
<p>Powered by <a href="http://rubyonrails.com/">Ruby on Rails</a></p>
</div> </div>
<%- end -%> <% end %>
</div> <!-- Content --> </div> <!-- Content -->

View file

@ -1,45 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg-flat.dtd" >
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>
<%= h("#{@status} #{@status_message}") %>
</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="robots" content="<%= @robots_metatag_value %>" />
<style type="text/css">
h1#pageName, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover, #TextileHelp h3 {
color: #<%= @web ? @web.color : "393" %>;
}
<%= Rails.root.join('public', 'stylesheets', 'instiki.css').read if @inline_style %>
</style>
<%= stylesheet_link_tag 'instiki', :media => 'all' unless @inline_style %>
<style type="text/css"><!--/*--><![CDATA[/*><!--*/
<%= @style_additions %>
<%= @web ? @web.additional_style : '' %>
/*]]>*/--></style>
</head>
<body>
<div id="Error-Container">
<h1>
<%= h("#{@status} #{@status_message}") %>
</h1>
<div id="Error-Content">
<%= if :raw
@content_for_layout
else
h @content_for_layout
end %>
</div> <!-- Error-Content -->
</div> <!-- Error-Container -->
</body>
</html>

View file

@ -1 +0,0 @@
<%= @content_for_layout %>

View file

@ -1,265 +0,0 @@
\documentclass[12pt,titlepage]{article}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{amsthm}
\usepackage{mathtools}
<%- if @tex_content =~ /\\mathbb\{[^}]*[a-z0-9]+[^}]*\}/ -%>
\usepackage{mathbbol}
<% end -%>
\usepackage{graphicx}
\usepackage{color}
\usepackage{ucs}
\usepackage[utf8x]{inputenc}
\usepackage{xparse}
\usepackage{hyperref}
%----Macros----------
%
% Unresolved issues:
%
% \righttoleftarrow
% \lefttorightarrow
%
% \color{} with HTML colorspec
% \bgcolor
% \array with options (without options, it's equivalent to the matrix environment)
% Of the standard HTML named colors, white, black, red, green, blue and yellow
% are predefined in the color package. Here are the rest.
\definecolor{aqua}{rgb}{0, 1.0, 1.0}
\definecolor{fuschia}{rgb}{1.0, 0, 1.0}
\definecolor{gray}{rgb}{0.502, 0.502, 0.502}
\definecolor{lime}{rgb}{0, 1.0, 0}
\definecolor{maroon}{rgb}{0.502, 0, 0}
\definecolor{navy}{rgb}{0, 0, 0.502}
\definecolor{olive}{rgb}{0.502, 0.502, 0}
\definecolor{purple}{rgb}{0.502, 0, 0.502}
\definecolor{silver}{rgb}{0.753, 0.753, 0.753}
\definecolor{teal}{rgb}{0, 0.502, 0.502}
% Because of conflicts, \space and \mathop are converted to
% \itexspace and \operatorname during preprocessing.
% itex: \space{ht}{dp}{wd}
%
% Height and baseline depth measurements are in units of tenths of an ex while
% the width is measured in tenths of an em.
\makeatletter
\newdimen\itex@wd%
\newdimen\itex@dp%
\newdimen\itex@thd%
\def\itexspace#1#2#3{\itex@wd=#3em%
\itex@wd=0.1\itex@wd%
\itex@dp=#2ex%
\itex@dp=0.1\itex@dp%
\itex@thd=#1ex%
\itex@thd=0.1\itex@thd%
\advance\itex@thd\the\itex@dp%
\makebox[\the\itex@wd]{\rule[-\the\itex@dp]{0cm}{\the\itex@thd}}}
\makeatother
% \tensor and \multiscript
\makeatletter
\newif\if@sup
\newtoks\@sups
\def\append@sup#1{\edef\act{\noexpand\@sups={\the\@sups #1}}\act}%
\def\reset@sup{\@supfalse\@sups={}}%
\def\mk@scripts#1#2{\if #2/ \if@sup ^{\the\@sups}\fi \else%
\ifx #1_ \if@sup ^{\the\@sups}\reset@sup \fi {}_{#2}%
\else \append@sup#2 \@suptrue \fi%
\expandafter\mk@scripts\fi}
\def\tensor#1#2{\reset@sup#1\mk@scripts#2_/}
\def\multiscripts#1#2#3{\reset@sup{}\mk@scripts#1_/#2%
\reset@sup\mk@scripts#3_/}
\makeatother
% \slash
\makeatletter
\newbox\slashbox \setbox\slashbox=\hbox{$/$}
\def\itex@pslash#1{\setbox\@tempboxa=\hbox{$#1$}
\@tempdima=0.5\wd\slashbox \advance\@tempdima 0.5\wd\@tempboxa
\copy\slashbox \kern-\@tempdima \box\@tempboxa}
\def\slash{\protect\itex@pslash}
\makeatother
% math-mode versions of \rlap, etc
% from Alexander Perlis, "A complement to \smash, \llap, and lap"
% http://math.arizona.edu/~aprl/publications/mathclap/
\def\clap#1{\hbox to 0pt{\hss#1\hss}}
\def\mathllap{\mathpalette\mathllapinternal}
\def\mathrlap{\mathpalette\mathrlapinternal}
\def\mathclap{\mathpalette\mathclapinternal}
\def\mathllapinternal#1#2{\llap{$\mathsurround=0pt#1{#2}$}}
\def\mathrlapinternal#1#2{\rlap{$\mathsurround=0pt#1{#2}$}}
\def\mathclapinternal#1#2{\clap{$\mathsurround=0pt#1{#2}$}}
% Renames \sqrt as \oldsqrt and redefine root to result in \sqrt[#1]{#2}
\let\oldroot\root
\def\root#1#2{\oldroot #1 \of{#2}}
\renewcommand{\sqrt}[2][]{\oldroot #1 \of{#2}}
% Manually declare the txfonts symbolsC font
\DeclareSymbolFont{symbolsC}{U}{txsyc}{m}{n}
\SetSymbolFont{symbolsC}{bold}{U}{txsyc}{bx}{n}
\DeclareFontSubstitution{U}{txsyc}{m}{n}
% Manually declare the stmaryrd font
\DeclareSymbolFont{stmry}{U}{stmry}{m}{n}
\SetSymbolFont{stmry}{bold}{U}{stmry}{b}{n}
% Declare specific arrows from txfonts without loading the full package
\makeatletter
\def\re@DeclareMathSymbol#1#2#3#4{%
\let#1=\undefined
\DeclareMathSymbol{#1}{#2}{#3}{#4}}
\re@DeclareMathSymbol{\neArrow}{\mathrel}{symbolsC}{116}
\re@DeclareMathSymbol{\neArr}{\mathrel}{symbolsC}{116}
\re@DeclareMathSymbol{\seArrow}{\mathrel}{symbolsC}{117}
\re@DeclareMathSymbol{\seArr}{\mathrel}{symbolsC}{117}
\re@DeclareMathSymbol{\nwArrow}{\mathrel}{symbolsC}{118}
\re@DeclareMathSymbol{\nwArr}{\mathrel}{symbolsC}{118}
\re@DeclareMathSymbol{\swArrow}{\mathrel}{symbolsC}{119}
\re@DeclareMathSymbol{\swArr}{\mathrel}{symbolsC}{119}
\re@DeclareMathSymbol{\nequiv}{\mathrel}{symbolsC}{46}
\re@DeclareMathSymbol{\Perp}{\mathrel}{symbolsC}{121}
\re@DeclareMathSymbol{\Vbar}{\mathrel}{symbolsC}{121}
\re@DeclareMathSymbol{\sslash}{\mathrel}{stmry}{12}
\re@DeclareMathSymbol{\invamp}{\mathrel}{symbolsC}{77}
\re@DeclareMathSymbol{\parr}{\mathrel}{symbolsC}{77}
\makeatother
% Widecheck
\makeatletter
\DeclareRobustCommand\widecheck[1]{{\mathpalette\@widecheck{#1}}}
\def\@widecheck#1#2{%
\setbox\z@\hbox{\m@th$#1#2$}%
\setbox\tw@\hbox{\m@th$#1%
\widehat{%
\vrule\@width\z@\@height\ht\z@
\vrule\@height\z@\@width\wd\z@}$}%
\dp\tw@-\ht\z@
\@tempdima\ht\z@ \advance\@tempdima2\ht\tw@ \divide\@tempdima\thr@@
\setbox\tw@\hbox{%
\raise\@tempdima\hbox{\scalebox{1}[-1]{\lower\@tempdima\box
\tw@}}}%
{\ooalign{\box\tw@ \cr \box\z@}}}
\makeatother
% \mathraisebox{voffset}[height][depth]{something}
\makeatletter
\NewDocumentCommand\mathraisebox{moom}{%
\IfNoValueTF{#2}{\def\@temp##1##2{\raisebox{#1}{$\m@th##1##2$}}}{%
\IfNoValueTF{#3}{\def\@temp##1##2{\raisebox{#1}[#2]{$\m@th##1##2$}}%
}{\def\@temp##1##2{\raisebox{#1}[#2][#3]{$\m@th##1##2$}}}}%
\mathpalette\@temp{#4}}
\makeatletter
% udots (taken from yhmath)
\makeatletter
\def\udots{\mathinner{\mkern2mu\raise\p@\hbox{.}
\mkern2mu\raise4\p@\hbox{.}\mkern1mu
\raise7\p@\vbox{\kern7\p@\hbox{.}}\mkern1mu}}
\makeatother
%% Fix array
\newcommand{\itexarray}[1]{\begin{matrix}#1\end{matrix}}
%% \itexnum is a noop
\newcommand{\itexnum}[1]{#1}
%% Renaming existing commands
\newcommand{\underoverset}[3]{\underset{#1}{\overset{#2}{#3}}}
\newcommand{\widevec}{\overrightarrow}
\newcommand{\darr}{\downarrow}
\newcommand{\nearr}{\nearrow}
\newcommand{\nwarr}{\nwarrow}
\newcommand{\searr}{\searrow}
\newcommand{\swarr}{\swarrow}
\newcommand{\curvearrowbotright}{\curvearrowright}
\newcommand{\uparr}{\uparrow}
\newcommand{\downuparrow}{\updownarrow}
\newcommand{\duparr}{\updownarrow}
\newcommand{\updarr}{\updownarrow}
\newcommand{\gt}{>}
\newcommand{\lt}{<}
\newcommand{\map}{\mapsto}
\newcommand{\embedsin}{\hookrightarrow}
\newcommand{\Alpha}{A}
\newcommand{\Beta}{B}
\newcommand{\Zeta}{Z}
\newcommand{\Eta}{H}
\newcommand{\Iota}{I}
\newcommand{\Kappa}{K}
\newcommand{\Mu}{M}
\newcommand{\Nu}{N}
\newcommand{\Rho}{P}
\newcommand{\Tau}{T}
\newcommand{\Upsi}{\Upsilon}
\newcommand{\omicron}{o}
\newcommand{\lang}{\langle}
\newcommand{\rang}{\rangle}
\newcommand{\Union}{\bigcup}
\newcommand{\Intersection}{\bigcap}
\newcommand{\Oplus}{\bigoplus}
\newcommand{\Otimes}{\bigotimes}
\newcommand{\Wedge}{\bigwedge}
\newcommand{\Vee}{\bigvee}
\newcommand{\coproduct}{\coprod}
\newcommand{\product}{\prod}
\newcommand{\closure}{\overline}
\newcommand{\integral}{\int}
\newcommand{\doubleintegral}{\iint}
\newcommand{\tripleintegral}{\iiint}
\newcommand{\quadrupleintegral}{\iiiint}
\newcommand{\conint}{\oint}
\newcommand{\contourintegral}{\oint}
\newcommand{\infinity}{\infty}
\newcommand{\bottom}{\bot}
\newcommand{\minusb}{\boxminus}
\newcommand{\plusb}{\boxplus}
\newcommand{\timesb}{\boxtimes}
\newcommand{\intersection}{\cap}
\newcommand{\union}{\cup}
\newcommand{\Del}{\nabla}
\newcommand{\odash}{\circleddash}
\newcommand{\negspace}{\!}
\newcommand{\widebar}{\overline}
\newcommand{\textsize}{\normalsize}
\renewcommand{\scriptsize}{\scriptstyle}
\newcommand{\scriptscriptsize}{\scriptscriptstyle}
\newcommand{\mathfr}{\mathfrak}
\newcommand{\statusline}[2]{#2}
\newcommand{\tooltip}[2]{#2}
\newcommand{\toggle}[2]{#2}
% Theorem Environments
\theoremstyle{plain}
\newtheorem{theorem}{Theorem}
\newtheorem{lemma}{Lemma}
\newtheorem{prop}{Proposition}
\newtheorem{cor}{Corollary}
\newtheorem*{utheorem}{Theorem}
\newtheorem*{ulemma}{Lemma}
\newtheorem*{uprop}{Proposition}
\newtheorem*{ucor}{Corollary}
\theoremstyle{definition}
\newtheorem{defn}{Definition}
\newtheorem{example}{Example}
\newtheorem*{udefn}{Definition}
\newtheorem*{uexample}{Example}
\theoremstyle{remark}
\newtheorem{remark}{Remark}
\newtheorem{note}{Note}
\newtheorem*{uremark}{Remark}
\newtheorem*{unote}{Note}
%-------------------------------------------------------------------
\begin{document}
%-------------------------------------------------------------------
<%= @content_for_layout %>
\end{document}

View file

@ -1,3 +0,0 @@
<h3>Markdown+itex2MML formatting tips (<a target="_new" href="http://daringfireball.net/projects/markdown/syntax">basics</a>, <a target="_new" href="http://michelf.com/projects/php-markdown/extra/">extended syntax</a>, <a target="_new" href="http://maruku.rubyforge.org/proposal.html">metadata</a>, <a target="_new" href="http://golem.ph.utexas.edu/instiki/show/Theorems">theorems</a>)</h3>
<p>For a complete list of LaTeX commands supported here, see the <a href="http://golem.ph.utexas.edu/~distler/blog/itex2MMLcommands.html">itex2MML Commands Summary</a>.</p>
<%= render(:file => "markdown_table") -%>

View file

@ -1,3 +0,0 @@
<h3>Markdown+blahtex/PNG formatting tips (<a target="_new" href="http://daringfireball.net/projects/markdown/syntax">basics</a>, <a target="_new" href="http://michelf.com/projects/php-markdown/extra/">extended syntax</a>, <a target="_new" href="http://maruku.rubyforge.org/proposal.html">metadata</a>)</h3>
<p>For a list of the LaTeX commands supported here, see the <a href="http://www.blahtex.org/manual.html">BlahTeX manual</a>.</p>
<%= render(:file => "markdown_table") -%>

View file

@ -1,2 +1,12 @@
<h3>Markdown formatting tips (<a target="_new" href="http://daringfireball.net/projects/markdown/syntax">basics</a>, <a target="_new" href="http://michelf.com/projects/php-markdown/extra/">extended syntax</a>, <a target="_new" href="http://maruku.rubyforge.org/proposal.html">metadata</a>)</h3> <h3>Markdown formatting tips (<a target="_new" href="http://daringfireball.net/projects/markdown/syntax">advanced</a>)</h3>
<%= render(:file => "markdown_table") -%> <table cellspacing="0" cellpadding="0">
<tr><td>_your text_</td><td class="arrow">&rarr;</td><td><em>your text</em></td></tr>
<tr><td>**your text**</td><td class="arrow">&rarr;</td><td><strong>your text</strong></td></tr>
<tr><td>`my code`</td><td class="arrow">&rarr;</td><td><code>my code</code></td></tr>
<tr><td>* Bulleted list<br />* Second item</td><td class="arrow">&rarr;</td><td>&#8226; Bulleted list<br />&#8226; Second item</td></tr>
<tr><td>1. Numbered list<br />1. Second item</td><td class="arrow">&rarr;</td><td>1. Numbered list<br />2. Second item</td></tr>
<tr><td>[link name](URL)</td><td class="arrow">&rarr;</td><td><a href="URL">link name</a></td></tr>
<tr><td>***</td><td class="arrow">&rarr;</td><td>Horizontal ruler</td></tr>
<tr><td>&lt;http://url><br />&lt;email@add.com></td><td class="arrow">&rarr;</td><td>Auto-linked</td></tr>
<tr><td>![Alt text](URL)</td><td class="arrow">&rarr;</td><td>Image</td></tr>
</table>

View file

@ -1,17 +0,0 @@
<table cellspacing="0" cellpadding="0">
<tr><td>_your text_</td><td class="arrow">&#x2192;</td><td><em>your text</em></td></tr>
<tr><td>**your text**</td><td class="arrow">&#x2192;</td><td><strong>your text</strong></td></tr>
<tr><td>`my code`</td><td class="arrow">&#x2192;</td><td><code>my code</code></td></tr>
<tr><td>* Bulleted list<br />* Second item</td><td class="arrow">&#x2192;</td><td>&#8226; Bulleted list<br />&#8226; Second item</td></tr>
<tr><td>1. Numbered list<br />1. Second item</td><td class="arrow">&#x2192;</td><td>1. Numbered list<br />2. Second item</td></tr>
<tr><td>Definition list<br />: is useful</td><td class="arrow">&#x2192;</td><td><dl style="display:inline"><dt>Definition list</dt><dd>is useful</dd></dl></td></tr>
<tr><td>[link name](URL)</td><td class="arrow">&#x2192;</td><td><a href="URL">link name</a></td></tr>
<tr><td>![Alt text](URL)</td><td class="arrow">&#x2192;</td><td>Image</td></tr>
<tr><td>## Header ##<br />### Subheader ###<br />#### Subsubhead####<br/>##### Etc. #####</td><td class="arrow">&#x2192;</td>
<td><b><span style="font-size:1.73em">Header</span>
<br/><span style="font-size:1.44em">Subheader</span><br/><span style="font-size:1.2em">Subsubhead</span>
<br/><span style="font-size:1em">Etc.</span></b></td></tr>
<tr><td>***</td><td class="arrow">&#x2192;</td><td>Horizontal rule</td></tr>
<tr><td>Some text[^fine] here.<br/>[^fine]: The fine print.</td><td class="arrow">&#x2192;</td><td>A footnote</td></tr>
<tr><td>*[LA]: Los Angeles<br/>*[ppm]: parts per million</td><td class="arrow">&#x2192;</td><td>Abbreviations</td></tr>
</table>

View file

@ -1,4 +1,4 @@
<%= render(:file => 'textile_help') %> <%= render 'textile_help' %>
<h3>Markdown</h3> <h3>Markdown</h3>
<p> <p>

View file

@ -8,24 +8,25 @@ def list_item(text, link_options, description, accesskey = nil)
end end
%> %>
<div class="navigation"> <%= form_tag({ :controller => 'wiki', :action => 'search', :web => @web.address},
<span class="skipNav"><a href='#navEnd'>Skip the Navigation Links</a> | </span> {'id' => 'navigationForm', 'class' => 'navigation', 'method' => 'get'})
<%- if params['action'] != 'published' then -%> %>
<% if @action_name != 'published' then %>
<%= list_item 'Home Page', {:action => 'show', :id => 'HomePage'}, 'Home, Sweet Home', 'H' %> | <%= list_item 'Home Page', {:action => 'show', :id => 'HomePage'}, 'Home, Sweet Home', 'H' %> |
<%= list_item 'All Pages', {:action => 'list'}, 'Alphabetically sorted list of pages', 'A' %> | <%= list_item 'All Pages', {:action => 'list'}, 'Alphabetically sorted list of pages', 'A' %> |
<%= list_item 'Recently Revised', {:action =>'recently_revised'}, 'Pages sorted by when they were last changed', 'U' %> | <%= list_item 'Recently Revised', {:action =>'recently_revised'},
'Pages sorted by when they were last changed', 'U'
%> |
<%= list_item 'Authors', {:action => 'authors'}, 'Who wrote what' %> | <%= list_item 'Authors', {:action => 'authors'}, 'Who wrote what' %> |
<%= list_item 'Feeds', {:action => 'feeds'}, 'Subscribe to changes by Atom' %> | <%= list_item 'Feeds', {:action => 'feeds'}, 'Subscribe to changes by RSS' %> |
<%= list_item 'Export', {:action => 'export'}, 'Download a zip with all the pages in this wiki', 'X' %> | <%= list_item 'Export', {:action => 'export'},
<% form_tag({ :controller => 'wiki', :action => 'search', :web => @web.address}, 'Download a zip with all the pages in this wiki', 'X'
{'id' => 'navigationSearchForm', 'method' => 'get', 'accept-charset' => 'utf-8' }) do %> %> |
<fieldset class="search"><input type="text" id="searchField" name="query" value="Search" <input type="text" id="searchField" name="query" style="font-size: 10px" value="Search"
onfocus="this.value == 'Search' ? this.value = '' : true" onfocus="if (this.value == 'Search' ) this.value = '' " />
onblur="this.value == '' ? this.value = 'Search' : true" /></fieldset> <% else %>
<% end %>
<%- else -%>
<%= list_item 'Home Page', {:action => 'published', :id => 'HomePage'}, 'Home, Sweet Home', 'H' %> | <%= list_item 'Home Page', {:action => 'published', :id => 'HomePage'}, 'Home, Sweet Home', 'H' %> |
<%= list_item 'Feeds', {:action => 'feeds'}, 'Subscribe to changes by Atom' %> <% end%>
<% end-%>
<span id='navEnd'></span> <%= end_form_tag %>
</div>

View file

@ -1,10 +0,0 @@
<span id="svg_logo"><svg version="1.1" width="100%" height="100%" viewBox='0 -1 180 198' xmlns='http://www.w3.org/2000/svg'>
<path id="svg_logo_path" fill="#<%= @web ? @web.color : "393" %>" stroke-width='0.5' stroke='#000' d='
M170,60c4,11-1,20-12,25c-9,4-25,3-20,15c5,5,15,0,24,1c11,1,21,11,14,21c-10,15-35,6-48-1c-5-3-27-23-32-10c-1,13,15,10,22,16
c11,4,24,14,34,20c12,10,7,25-9,23c-11-1-22-9-30-16c-5-5-13-18-21-9c-2,6,2,11,5,14c9,9,22,14,22,31c-2,8-12,8-18,4c-4-3-9-8-11-13
c-3-6-5-18-12-18c-14-1-5,28-18,30c-9,2-13-9-12-16c1-14,12-24,21-31c5-4,17-13,10-20c-9-10-19,12-23,16c-7,7-17,16-31,15
c-9-1-18-9-11-17c5-7,14-4,23-6c6-1,15-8,8-15c-5-6-57,2-42-24c7-12,51,4,61,6c6,1,17,4,18-4c2-11-12-7-21-8c-21-2-49-14-49-34
c0-5,3-11,8-11C31,42,34,65,42,67c6,1,9-3,8-9C49,49,38,40,40,25c1-5,4-15,13-14c10,2,11,18,13,29c1,8,0,24,7,28c15,0,5-22,4-30
C74,23,78,7,87,1c8-4,14,1,16,9c2,11-8,21-2,30c8,2,11-6,14-12c9-14,36-18,30,5c-3,9-12,19-21,24c-6,4-22,10-23,19c-2,14,15,2,18-2
c9-9,20-18,33-22C159,52,166,54,170,60' />
</svg></span>

View file

@ -1,19 +1,20 @@
<h3>Textile formatting tips (<a href="http://redcloth.org/hobix.com/textile/quick.html" onclick="quickRedReference(); return false;">advanced</a>)</h3> <h3>Textile formatting tips (<a href="http://hobix.com/textile/quick.html" onClick="quickRedReference(); return false;">advanced</a>)</h3>
<table cellspacing="0" cellpadding="0"> <table cellspacing="0" cellpadding="0">
<tr><td>_your text_</td><td class="arrow">&rarr;</td><td><em>your text</em></td></tr> <tr><td>_your text_</td><td class="arrow">&rarr;</td><td><em>your text</em></td></tr>
<tr><td>*your text*</td><td class="arrow">&rarr;</td><td><strong>your text</strong></td></tr> <tr><td>*your text*</td><td class="arrow">&rarr;</td><td><strong>your text</strong></td></tr>
<tr><td style="padding-right:0.75em">%{color:red}hello%</td><td class="arrow">&rarr;</td><td><span style="color: red;">hello</span></td></tr> <tr><td>%{color:red}hello%</td><td class="arrow">&rarr;</td><td><span style="color: red;">hello</span></td></tr>
<tr><td>* Bulleted list<br />* Second item</td><td class="arrow">&rarr;</td><td>&#8226; Bulleted list<br />&#8226; Second item</td></tr> <tr><td>* Bulleted list<br />* Second item</td><td class="arrow">&rarr;</td><td>&#8226; Bulleted list<br />&#8226; Second item</td></tr>
<tr><td># Numbered list<br /># Second item</td><td class="arrow">&rarr;</td><td>1. Numbered list<br />2. Second item</td></tr> <tr><td># Numbered list<br /># Second item</td><td class="arrow">&rarr;</td><td>1. Numbered list<br />2. Second item</td></tr>
<tr><td>"linkname":URL</td><td class="arrow">&rarr;</td><td><a href="URL">linkname</a></td></tr> <tr><td>"linkname":URL</td><td class="arrow">&rarr;</td><td><a href="URL">linkname</a></td></tr>
<tr><td>|a|table|row|<br />|b|table|row|</td><td class="arrow">&rarr;</td><td>Table</td></tr> <tr><td>|a|table|row|<br />|b|table|row|</td><td class="arrow">&rarr;</td><td>Table</td></tr>
<tr><td>http://url<br />email@address.com</td><td class="arrow">&rarr;</td><td>Auto-linked</td></tr>
<tr><td>!imageURL!</td><td class="arrow">&rarr;</td><td>Image</td></tr> <tr><td>!imageURL!</td><td class="arrow">&rarr;</td><td>Image</td></tr>
</table> </table>
<script language="JavaScript"> <script language="JavaScript">
function quickRedReference() { function quickRedReference() {
window.open( window.open(
"http://redcloth.org/hobix.com/textile/quick.html", "http://hobix.com/textile/quick.html",
"redRef", "redRef",
"height=600,width=550,channelmode=0,dependent=0," + "height=600,width=550,channelmode=0,dependent=0," +
"directories=0,fullscreen=0,location=0,menubar=0," + "directories=0,fullscreen=0,location=0,menubar=0," +

View file

@ -1,13 +0,0 @@
<%- unless @page.linked_from.empty? -%>
<span class="linked">
| Linked from:
<%= @page.linked_from.collect { |referring_page| link_to_existing_page referring_page }.join(", ").html_safe %>
</span>
<%- end -%>
<%- unless @page.included_from.empty? -%>
<span class="linked">
| Included from:
<%= @page.included_from.collect { |referring_page| link_to_existing_page referring_page }.join(", ").html_safe %>
</span>
<%- end -%>

View file

@ -1,31 +0,0 @@
xml.feed('xmlns' => "http://www.w3.org/2005/Atom", "xml:lang" => 'en') do
xml.title(@web.name)
xml.link( 'rel' => 'alternate', 'type' => "application/xhtml+xml", 'href' => url_for(:only_path => false, :web => @web_name, :action => @link_action, :id => 'HomePage') )
xml.link( 'rel' => 'self', 'href' => url_for(:only_path => false, :web => @web_name, :action => @hide_description? :atom_with_headlines : :atom_with_content ) )
xml.updated(@web.revised_at.getgm.strftime("%Y-%m-%dT%H:%M:%SZ") )
xml.id('tag:' + url_for(:only_path => false, :web => @web_name).split('/')[2].split(':')[0] + ',' + @web.created_at.getgm.strftime("%Y-%m-%d") + ':' + CGI.escape(@web.name) )
xml.subtitle('An Instiki Wiki')
xml.generator('Instiki', 'uri' => "http://golem.ph.utexas.edu/instiki/show/HomePage", 'version' => Instiki::VERSION::STRING)
for page in @pages_by_revision
xml.entry do
xml.title(page.plain_name, 'type' => "html")
xml.link('rel' => 'alternate', 'type' => 'application/xhtml+xml', 'href' => url_for(:only_path => false, :web => @web_name, :action => @link_action, :id => page.name) )
xml.updated(page.revised_at.getgm.strftime("%Y-%m-%dT%H:%M:%SZ") )
xml.published(page.created_at.getgm.strftime("%Y-%m-%dT%H:%M:%SZ") )
xml.id('tag:' +url_for(:only_path => false, :web => @web_name).split('/')[2].split(':')[0] + ',' + page.created_at.getgm.strftime("%Y-%m-%d") + ":" + @web.name + ',' + CGI.escape(page.name))
xml.author do
xml.name(page.author)
end
if @hide_description
xml.summary("Updated by #{page.author} on #{page.revised_at.getgm.strftime("%Y-%m-%d")} at #{page.revised_at.getgm.strftime("%H:%M:%SZ")}.", 'type' => 'text')
else
xml.content('type' => 'xhtml', 'xml:base' => url_for(:only_path => false, :web => @web_name, :action => @link_action, :id => page.name) ) do
xml.div('xmlns' => 'http://www.w3.org/1999/xhtml' ) do
|x| x << rendered_content(page)
end
end
end
end
end
end

View file

@ -1,11 +1,11 @@
<%- @title = 'Authors' -%> <% @title = 'Authors' %>
<ul id="authorList"> <ul id="authorList">
<%- for author in @authors -%> <% for author in @authors %>
<li> <li>
<%= link_to_page author.purify %> <%= link_to_page author %>
co- or authored: co- or authored:
<%= raw @page_names_by_author[author].collect { |page_name| link_to_page(page_name) }.sort.join ', ' %> <%= @web.select.pages_authored_by(author).collect { |page| link_to_page(page.name) }.sort.join ', ' %>
</li> </li>
<%- end -%> <% end %>
</ul> </ul>

View file

@ -1,79 +1,37 @@
<%- <%
@title = "Editing #{@page.name.escapeHTML}".html_safe @title = "Editing #{@page.name}"
@content_width = 720 @content_width = 720
@hide_navigation = true @hide_navigation = true
-%> %>
<div id="MarkupHelp"> <div id="MarkupHelp" style="float: right; width: 250px; margin-top: 5px">
<%= render(:file => "#{@web.markup}_help") -%> <%= render("#{@web.markup}_help") %>
<%= render(:file => 'wiki_words_help') unless @web.brackets_only? -%> <%= render 'wiki_words_help' %>
</div> </div>
<% form_tag({ :action => 'save', :web => @web.address, :id => @page.name }, <%= form_tag({ :action => 'save', :web => @web.address, :id => @page.name},
{ 'id' => 'editForm', 'method' => 'post', 'onsubmit' => 'cleanAuthorName()', {'id' => 'editForm', 'method' => 'post', 'onSubmit' => 'cleanAuthorName()'})
'accept-charset' => 'utf-8' }) do %> %>
<div>
<textarea name="content" id="content" rows="24" cols="60"><%= h(flash[:content] ||
(params['content'] ? params['content'] : @page.content).purify) %></textarea>
<% if @page_name != 'HomePage' -%>
<p> <p>
<%= check_box_tag :alter_title, value = "1", checked=false, <textarea name="content" style="width: 450px; height: 500px"><%= h(@flash[:content] || @page.content) %></textarea>
'onchange' => "toggleVisibility();" %> <label for="alter_title">Change page name.</label><br/>
<span id="title_change" style="display:none"><label for="new_name">New name:</label> <%= text_field_tag :new_name, h(@page.name.purify),
:onblur => "addRedirect();" %></span>
</p> </p>
<% else -%> <p>
<%= hidden_field_tag 'new_name', @page_name %>
<% end%>
<div id="editFormButtons">
<input type="submit" value="Submit" accesskey="s"/> as <input type="submit" value="Submit" accesskey="s"/> as
<%= text_field_tag :author, h(@author.purify), <input type="text" name="author" id="authorName" value="<%= @author %>"
:onfocus => "this.value == 'AnonymousCoward' ? this.value = '' : true;", onClick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
:onblur => "this.value == '' ? this.value = 'AnonymousCoward' : true" %>
| |
<span>
<%= link_to('Cancel', {:web => @web.address, :action => 'cancel_edit', :id => @page.name}, <%= link_to('Cancel', {:web => @web.address, :action => 'cancel_edit', :id => @page.name},
{:accesskey => 'c'}) %> {:accesskey => 'c'})
<span class="unlock">(unlocks page)</span> %>
</span> <small>(unlocks page)</small>
</div> </p>
</div> <%= end_form_tag %>
<%- end -%>
<script type="text/javascript">
<!--//--><![CDATA[//><!--
function toggleVisibility() {
var span = document.getElementById('title_change');
if (span.style.display =='inline') {
span.style.display ='none';
document.getElementById('new_name').value = "<%= escape_javascript(@page.name) %>";
var content = document.getElementById('content').value
document.getElementById('content').value = content.replace(/\[\[!redirects <%= Regexp.escape(@page.name).gsub('/', '\\/') %>\]\]\n/, '')
}
else
span.style.display ='inline'
}
function addRedirect(){
var e = document.getElementById('new_name').value;
if ( e != "<%= escape_javascript(@page.name) %>" && e != '') {
var content = document.getElementById('content');
content.value = '[[!redirects <%= escape_javascript(@page.name) %>]]\n' + content.value
}
}
<script language="JavaScript1.2">
function cleanAuthorName() { function cleanAuthorName() {
if (document.getElementById('authorName').value == "") { if (document.getElementById('authorName').value == "") {
document.getElementById('authorName').value = 'AnonymousCoward'; document.getElementById('authorName').value = 'AnonymousCoward';
} }
} }
document.forms["editForm"].elements["content"].focus();
<%- if [:markdownMML, :markdownPNG, :markdown].include?(@web.markup) -%>
setupSVGedit('<%= compute_public_path("editor/svg-editor.html", "svg-edit").split(/\?/)[0] %>');
<%- unless @page.categories.include?('S5-slideshow') -%>
addS5button('<%= @page.name.escapeHTML %>');
<%- end -%>
<%- end -%>
//--><!]]>
</script> </script>

View file

@ -1,8 +1,12 @@
<%- @title = "Export" -%> <% @title = "Export" %>
<p>You can export all the pages in this web as a zip file in either HTML (with working links and all) or the pure markup (to import in another wiki).</p> <p>You can export all the pages in this web as a zip file in either HTML (with working links and all) or the pure markup (to import in another wiki).</p>
<ul id="feedsList"> <ul id="feedsList">
<li><%= link_to 'HTML', :web => @web.address, :action => 'export_html' %></li> <li><%= link_to 'HTML', :web => @web.address, :action => 'export_html' %></li>
<li><%= link_to "Markup (#{@web.markup.to_s.capitalize})", :web => @web.address, :action => 'export_markup' %></li> <li><%= link_to "Markup (#{@web.markup.to_s.capitalize})", :web => @web.address, :action => 'export_markup' %></li>
<% if OPTIONS[:pdflatex] && @web.markup == :textile %>
<li><%= link_to 'TeX', :web => @web.address, :action => 'export_tex' %></li>
<li><%= link_to 'PDF', :web => @web.address, :action => 'export_pdf' %></li>
<% end %>
</ul> </ul>

View file

@ -1,14 +1,14 @@
<%- @title = "Feeds" -%> <% @title = "Feeds" %>
<p>You can subscribe to this wiki's Atom feed and get either just the headlines of the pages that change or the entire page.</p> <p>You can subscribe to this wiki by RSS and get either just the headlines of the pages that change or the entire page.</p>
<ul id="feedsList"> <ul id="feedsList">
<%- if @rss_with_content_allowed -%>
<li> <li>
<%= link_to 'Full content (Atom 1.0)', :web => @web.address, :action => :atom_with_content %> <% if @rss_with_content_allowed %>
<%= link_to 'Full content (RSS 2.0)', :web => @web.address, :action => :rss_with_content %>
<% end %>
</li> </li>
<%- end -%>
<li> <li>
<%= link_to 'Headlines (Atom 1.0)', :web => @web.address, :action => :atom_with_headlines %> <%= link_to 'Headlines (RSS 2.0)', :web => @web.address, :action => :rss_with_headlines %>
</li> </li>
</ul> </ul>

View file

@ -1,32 +0,0 @@
<%- @title = "Uploaded Files" -%>
<% form_tag({ :controller => 'admin', :action => 'delete_files', :web => @web.address },
{'method' => 'post', 'accept-charset' => 'utf-8' }) do
%>
<div id="allFiles">
<h2>
Uploaded Files in <%= @web.name %>
</h2>
<%- if @file_list.empty? -%>
<p>No uploaded files in this Web</p>
<%- else -%>
<p>Check the files you wish to delete (<%= link_to "sort by #{@alt_sort_name}", :controller => 'wiki',
:web => @web.address, :action => 'file_list', :sort_order => @alt_sort_order %>).</p>
<ul style="list-style-type:none">
<%- for file in @file_list -%>
<li>
<input type="checkbox" name="<%= file.file_name %>" value="delete"/>
<a href="<%= url_for :web => @web.address, :action => 'files',
:id => file.file_name %>"><%= file.file_name%></a> (<%= file.created_at.asctime %>) <span class="linked"><%= "Linked to by: " unless
@web.pages_that_link_to_file(file.file_name).empty? -%>
<%= @web.pages_that_link_to_file(file.file_name).collect { |referring_page| link_to_page(referring_page) }.join(", ").html_safe %></span>
</li>
<%- end -%>
</ul>
<label for="system_password">Enter system password</label>
<%= password_field_tag "system_password", '', :class => "disableAutoComplete" %>
and <%= submit_tag("Delete Files") %>
<%- end -%>
</div>
<%- end -%>

View file

@ -1,28 +0,0 @@
<%- @title = @page.plain_name + " (history)".html_safe -%>
<%- @show_footer = true -%>
<%- @revisions_by_day.keys.sort.reverse.each do |day| -%>
<h3><%= format_date(day, include_time = false) %></h3>
<ul>
<%- for rev in @revisions_by_day[day] -%>
<li>
<%= link_to_revision(rev.page, @revision_numbers[rev.id],
text= (rev.page.rev_ids.size == @revision_numbers[rev.id] ?
"Current" :
"Revision #{@revision_numbers[rev.id]}" )
) %>
<%- if @revision_numbers[rev.id] > 1 -%>
<span class="hist_views">
(<%= link_to_revision(rev.page, @revision_numbers[rev.id],
text="diff", mode='diff') %>)
</span>
<%- end -%>
<div class="byline" style="margin-bottom: 0px">
by <%= link_to_page(rev.author) %>
at <%= format_date(rev.revised_at) %>
<%= "from #{rev.author.ip}" if rev.author.respond_to?(:ip) %>
</div>
</li>
<%- end -%>
</ul>
<%- end -%>

View file

@ -1,90 +1,64 @@
<%- @title = "All Pages" -%> <% @title = "All Pages" %>
<%= categories_menu unless @categories.empty? %> <%= categories_menu unless @categories.empty? %>
<div id="allPages"> <div id="allPages" style="float: left; width: 280px; margin-right: 30px">
<%- unless @pages_that_are_orphaned.empty? && @page_names_that_are_wanted.empty? -%> <% unless @pages_that_are_orphaned.empty? && @page_names_that_are_wanted.empty? %>
<h2> <h2>
All Pages All Pages
<br/><span class="pageType">All pages in <%= raw @set_name %> listed alphabetically</span> <br/><small style="font-size: 12px"><i>All pages in <%= @set_name %> listed alphabetically</i></small>
</h2> </h2>
<%- end -%>
<% if [:markdownMML, :markdownPNG, :markdown].include?(@web.markup) -%>
<% form_tag({ :controller => 'wiki', :action => 'tex_list', :web => @web.address },
{'method' => 'post', 'accept-charset' => 'utf-8' }) do
%>
<div>
<% if params['category'] -%>
<input type="hidden" name="category" value="<%= params['category'] %>"/>
<%- end -%>
<ul id="sortable_pages">
<% @pages_in_category.each do |page| %>
<% content_tag_for :li, page do %>
<input type="checkbox" name="<%= page.name %>" value="tex"/>
<%= link_to_existing_page page, truncate(page.plain_name, :length => 35) %>
<% end %> <% end %>
<% end %>
</ul>
<%= sortable_element('sortable_pages', {:onUpdate => 'function(){}'}) %>
<label for="commit"> Export selected pages (drag to re-order them) to a LaTeX file.</label>
<%= submit_tag("Export") %>
</div>
<%- end -%>
<%- else -%>
<ul> <ul>
<%- @pages_in_category.each do |page| -%> <% @pages_by_name.each do |page| %>
<li> <li>
<%= link_to_existing_page page, truncate(page.plain_name, :length => 35) %> <%= link_to_existing_page page, truncate(page.plain_name, 35) %>
</li> </li>
<%- end -%> <% end %></ul>
</ul>
<%- end -%>
<%- if @web.count_pages? -%> <% if @web.count_pages %>
<% total_chars = @pages_in_category.characters %> <% total_chars = @pages_in_category.characters %>
<p class="pageStats">All content: <%= total_chars %> chars / approx. <%= sprintf("%-.1f", (total_chars / 2275 )) %> printed pages</p> <p><small>All content: <%= total_chars %> chars / <%= sprintf("%-.1f", (total_chars / 2275 )) %> pages</small></p>
<%- end -%> <% end %>
</div> </div>
<div id="wantedPages"> <div style="float: left; width: 280px">
<%- unless @page_names_that_are_wanted.empty? -%> <% unless @page_names_that_are_wanted.empty? %>
<h2> <h2>
Wanted Pages Wanted Pages
<br/> <br/>
<span class="pageType"> <small style="font-size: 12px">
Nonexistent pages that other pages in <%= raw @set_name %> reference <i>Unexisting pages that other pages in <%= @set_name %> reference</i>
</span> </small>
</h2> </h2>
<ul style="margin-bottom: 10px"> <ul style="margin-bottom: 10px">
<%- @page_names_that_are_wanted.each do |wanted_page_name| -%> <% @page_names_that_are_wanted.each do |wanted_page_name| %>
<li> <li>
<%= link_to_page(wanted_page_name, @web, truncate(WikiWords.separate(wanted_page_name), :length => 35)) %> <%= link_to_page(wanted_page_name, @web, truncate(WikiWords.separate(wanted_page_name), 35)) %>
wanted by wanted by
<%= @web.select.pages_that_reference(wanted_page_name).collect { |referring_page| <%= @web.select.pages_that_reference(wanted_page_name).collect { |referring_page|
link_to_existing_page referring_page link_to_existing_page referring_page
}.join(", ").html_safe }.join(", ")
%> %>
</li> </li>
<%- end -%> <% end %>
</ul> </ul>
<%- end -%> <% end %>
<%- unless @pages_that_are_orphaned.empty? -%> <% unless @pages_that_are_orphaned.empty? %>
<h2> <h2>
Orphaned Pages Orphaned Pages
<br/><span class="pageType">Pages in <%= raw @set_name %> that no other page reference</span> <br/><small style="font-size: 12px"><i>Pages in <%= @set_name %> that no other page reference</i></small>
</h2> </h2>
<ul style="margin-bottom: 35px"> <ul style="margin-bottom: 35px">
<%- @pages_that_are_orphaned.each do |orphan_page| -%> <% @pages_that_are_orphaned.each do |orphan_page| %>
<li> <li>
<%= link_to_existing_page orphan_page, truncate(orphan_page.plain_name, :length => 35) %> <%= link_to_existing_page orphan_page, truncate(orphan_page.plain_name, 35) %>
</li> </li>
<%- end -%> <% end %>
</ul> </ul>
<%- end -%> <% end %>
</div> </div>

View file

@ -1,12 +1,12 @@
<%- @title = "#{@page.plain_name} is locked".html_safe -%> <% @title = "#{@page.plain_name} is locked" %>
<p> <p>
<%= link_to_page(h(@page.locked_by.purify)) %> <%= link_to_page(@page.locked_by) %>
<%- if @page.lock_duration(Time.now) == 0 -%> <% if @page.lock_duration(Time.now) == 0 %>
just started editing this page. just started editing this page.
<%- else -%> <% else %>
has been editing this page for <%= @page.lock_duration(Time.now) %> minutes. has been editing this page for <%= @page.lock_duration(Time.now) %> minutes.
<%- end -%> <% end %>
</p> </p>
<p> <p>

View file

@ -1,20 +1,14 @@
<%- @title = "#{@web_name} Login" %><% @hide_navigation = true -%> <% @title = "#{@web_name} Login" %><% @hide_navigation = true %>
<% form_tag({ :controller => 'wiki', :action => 'authenticate', :web => @web.address},
{ 'id' => 'loginForm', 'method' => 'post', 'accept-charset' => 'utf-8' }) do %>
<p> <p>
This web is password-protected. Please enter the password. <%= form_tag({ :controller => 'wiki', :action => 'authenticate', :web => @web.address},
<% if @web.published? %> { 'name' => 'loginForm', 'id' => 'loginForm', 'method' => 'post'})
If you don't have the password, you can view this wiki as a <%= link_to 'read-only version', :action => 'published', :id => 'HomePage' %>. %>
<% end %> <b>Password</b><br />
<input type="password" name="password" id="password" default="yes" />
<%= end_form_tag %>
</p> </p>
<p>
<label for="password"><b>Password:</b></label>
<input type="password" name="password" id="password" />
<input type="submit" value="Login" />
</p>
<%- end -%>
<script type="text/javascript"> <script language="JavaScript">
document.forms["loginForm"].elements["password"].focus(); document.forms["loginForm"].elements["password"].focus();
</script> </script>

View file

@ -1,38 +1,31 @@
<%- <%
@title = "Creating #{WikiWords.separate(@page_name).escapeHTML}".html_safe @title = "Creating #{WikiWords.separate(@page_name)}"
@content_width = 720 @content_width = 720
@hide_navigation = true @hide_navigation = true
-%> %>
<div id="MarkupHelp"> <div id="MarkupHelp" style="float: right; width: 250px; margin-top: 5px">
<%= render(:file => "#{@web.markup}_help") -%> <%= render("#{@web.markup}_help") %>
<%= render(:file => 'wiki_words_help') unless @web.brackets_only? -%> <%= render 'wiki_words_help' %>
</div> </div>
<div id="editForm"> <%= form_tag({ :action => 'save', :web => @web.address, :id => @page_name},
<% form_tag({ :action => 'save', :web => @web.address, :id => @page_name }, {'id' => 'editForm', 'method' => 'post', 'onSubmit' => 'cleanAuthorName();'})
{ 'id' => 'editForm', 'method' => 'post', 'onsubmit' => 'cleanAuthorName();', 'accept-charset' => 'utf-8' }) do %> %>
<textarea name="content" id="content" rows="24" cols="60"><%= h(flash[:content] || <p>
params['content'] ? params['content'].purify : '' ) %></textarea> <textarea name="content" style="width: 450px; height: 500px"><%= h(@flash[:content] || '') %></textarea>
<div id="editFormButtons"> </p>
<p>
<input type="submit" value="Submit" accesskey="s"/> as <input type="submit" value="Submit" accesskey="s"/> as
<%= text_field_tag :author, @author, <input type="text" name="author" id="authorName" value="<%= @author %>" onClick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
:onfocus => "this.value == 'AnonymousCoward' ? this.value = '' : true;", </p>
:onblur => "this.value == '' ? this.value = 'AnonymousCoward' : true" %> <%= end_form_tag %>
</div>
<%- end -%>
</div>
<script type="text/javascript"> <script language="JavaScript1.2">
function cleanAuthorName() { function cleanAuthorName() {
if (document.getElementById('authorName').value == "") { if (document.getElementById('authorName').value == "") {
document.getElementById('authorName').value = 'AnonymousCoward'; document.getElementById('authorName').value = 'AnonymousCoward';
} }
} }
document.forms["editForm"].elements["content"].focus();
<%- if [:markdownMML, :markdownPNG, :markdown].include?(@web.markup) -%>
setupSVGedit('<%= compute_public_path("editor/svg-editor.html", "svg-edit").split(/\?/)[0] %>');
addS5button('<%= @page_name.escapeHTML %>');
<%- end -%>
</script> </script>

View file

@ -1,54 +1,120 @@
<%- <%
@title = @page.plain_name @title = @page.plain_name
@title += ' (changes)' if @show_diff
@show_footer = true @show_footer = true
-%> %>
<div id="revision"> <div id="revision">
<%- if @show_diff -%> <%= @page.display_content %>
<p class="show_diff">
Showing changes from revision #<%= @page.rev_ids.size - 1 %> to #<%= @page.rev_ids.size %>:
<ins class="diffins">Added</ins> | <del class="diffdel">Removed</del> | <del class="diffmod">Chan</del><ins class="diffmod">ged</ins>
</p>
<%= @renderer.display_diff %>
<%- else -%>
<%= @renderer.display_content %>
<%- end -%>
</div> </div>
<% if @page.revisions.length > 1 %>
<div id="changes" style="display: none">
<p style="background: #eee; padding: 3px; border: 1px solid silver">
<small>
Showing changes from revision #<%= @page.number - 1 %> to #<%= @page.number %>:
<ins class="diffins">Added</ins> | <del class="diffdel">Removed</del>
</small>
</p>
<%= @page.display_diff %>
</div>
<% end %>
<div class="byline"> <div class="byline">
<%= @page.revisions? ? "Revised" : "Created" %> on <%= format_date(@page.revised_at) %> <%= @page.revisions? ? "Revised" : "Created" %> on <%= @page.pretty_created_at %>
by <%= author_link(@page) %> by <%= @page.author_link %>
<%= "(#{@page.author.ip})" if @page.author.respond_to?(:ip) %> <%= "(#{@page.author.ip})" if @page.author.respond_to?(:ip) %>
<% if @web.count_pages? %> <% if @web.count_pages %>
<% total_chars = @page.content.length %> <% total_chars = @page.content.length %>
(<%= total_chars %> characters / <%= sprintf("%-.1f", (total_chars / 2275 rescue 0)) %> pages) (<%= total_chars %> characters / <%= sprintf("%-.1f", (total_chars / 2275 rescue 0)) %> pages)
<% end %> <% end %>
</div> </div>
<div class="navigation navfoot"> <div class="navigation">
<% if @page.name == "HomePage" %>
<%= link_to('Edit Page',
{:web => @web.address, :action => 'edit', :id => @page.name},
{:class => 'navlink', :accesskey => 'E', :name => 'edit'})
%>
|
<%= link_to('Edit Web',
{:web => @web.address, :action => 'edit_web'},
{:class => 'navlink', :name => 'edit_web'})
%>
<% else %>
<%= link_to('Edit',
{:web => @web.address, :action => 'edit', :id => @page.name},
{:class => 'navlink', :accesskey => 'E', :name => 'edit'})
%>
<% end %>
<%= raw navigation_menu_for_page.join(' | ') %> <% if @page.revisions.length > 1 %>
|
<%= link_to('Back in time',
{:web => @web.address, :action => 'revision', :id => @page.name,
:rev => @page.revisions.length - 2},
{:class => 'navlink', :accesskey => 'R', :name => 'to_previous_revision'})
%>
<small>(<%= @page.revisions.length - 1 %> revisions)</small>
<% end %>
<span class="views"> <% if @page.revisions.length > 1 %>
<span id="show_changes">
| <a href="#" name="see_changes" onClick="toggleChanges(); return false;">See changes</a>
</span>
<span id="hide_changes" name="hide_changes" style="display: none">
| <a href="#" onClick="toggleChanges(); return false;">Hide changes</a>
</span>
<% end %>
<small>
| Views: | Views:
<%= link_to('Print', <%= link_to('Print',
{:web => @web.address, :action => 'print', :id => @page.name}, {:web => @web.address, :action => 'print', :id => @page.name},
{ :accesskey => 'p', :id => 'view_print', :rel => 'nofollow' }) %> {:accesskey => 'p', :name => 'view_print'}) %>
<%- if @web.markup == :markdownMML or @web.markup == :markdown or @web.markup == :markdownPNG -%> <% if defined? RedClothForTex and RedClothForTex.available? and @web.markup == :textile %>
| |
<%= link_to 'TeX', {:web => @web.address, :action => 'tex', :id => @page.name}, <%= link_to 'TeX', {:web => @web.address, :action => 'tex', :id => @page.name},
{:id => 'view_tex', :rel => 'nofollow' } %> {:name => 'view_tex'} %>
<% if WikiReference.pages_in_category(@web, 'S5-slideshow').map.include?(@page.name) %>
| |
<%= link_to 'S5', {:web => @web.address, :action => 's5', :id => @page.name}, <%= link_to 'PDF', {:web => @web.address, :action => 'pdf', :id => @page.name},
{:id => 'view_S5'} %> {:name => 'view_pdf'} %>
<%- end -%> <% end %>
<%- end -%> </small>
|
<%= link_to 'Source', {:web => @web.address, :action => 'source', :id => @page.name},
{:id => 'view_source', :rel => 'nofollow' } %>
</span>
<%= render :partial => 'inbound_links' %> <% unless @page.linked_from.empty? %>
<small>
| Linked from:
<%= @page.linked_from.collect { |referring_page|
link_to_existing_page referring_page
}.join(", ")
%>
</small>
<% end %>
<% if @page.included_from.length > 0 %>
<small>
| Included from: <%= @page.included_from.collect { |referring_page|
link_to_existing_page referring_page
}.join(", ")
%>
</small>
<% end %>
</div> </div>
<script language="Javascript" type="text/Javascript">
function toggleChanges() {
if (document.getElementById("changes").style.display == "none") {
document.getElementById("changes").style.display = "block";
document.getElementById("revision").style.display = "none";
document.getElementById("show_changes").style.display = "none";
document.getElementById("hide_changes").style.display = "inline";
} else {
document.getElementById("changes").style.display = "none";
document.getElementById("revision").style.display = "block";
document.getElementById("show_changes").style.display = "inline";
document.getElementById("hide_changes").style.display = "none";
}
}
</script>

View file

@ -1,13 +1,14 @@
<%- <%
@title = @page.plain_name @title = @page.plain_name
@hide_navigation = true @hide_navigation = true
@style_additions = ".newWikiWord { background-color: white; font-style: italic; }" @style_additions = ".newWikiWord { background-color: white; font-style: italic; }"
-%> @inline_style = true
%>
<%= @renderer.display_content %> <%= @page.display_content_for_export %>
<div class="byline"> <div class="byline">
<%= @page.revisions? ? "Revised" : "Created" %> on <%= format_date(@page.revised_at) %> <%= @page.revisions? ? "Revised" : "Created" %> on <%= @page.pretty_created_at %>
by by
<%= author_link(@page, { :mode => (@link_mode || :show) }) %> <%= @page.author_link({ :mode => (@link_mode || :show) }) %>
</div> </div>

View file

@ -1,25 +1,9 @@
<%- <%
@title = @page.plain_name @title = @page.plain_name
@hide_navigation = false @hide_navigation = false
@style_additions = ".newWikiWord { background-color: white; font-style: italic; }" @style_additions = ".newWikiWord { background-color: white; font-style: italic; }"
@inline_style = false @inline_style = true
@show_footer = true @show_footer = true
-%> %>
<%= @renderer.display_published %> <%= @page.display_published %>
<div class="byline">
<%= @page.revisions? ? "Revised" : "Created" %> on <%= format_date(@page.revised_at) %>
by
<%= author_link(@page, { :mode => (@link_mode || :show) }) %>
</div>
<div class="navigation navfoot">
<span class="views">
<%- if WikiReference.pages_in_category(@web, 'S5-slideshow').map.include?(@page.name) -%>
Views:
<%= link_to 'S5', {:web => @web.address, :action => 's5',
:id => @page.name}, {:id => 'view_S5'} %>
<%- end -%>
</span>
</div>

View file

@ -1,25 +1,27 @@
<%- @title = "Recently Revised" -%> <% @title = "Recently Revised" %>
<%= categories_menu %> <%= categories_menu %>
<%- @pages_by_day.keys.sort.reverse.each do |day| -%> <% unless @pages_by_revision.empty? %>
<h3><%= format_date(day, include_time = false) %></h3> <% revision_date = @pages_by_revision.first.revised_on %>
<h3><%= revision_date.strftime('%B %e, %Y') %></h3>
<ul> <ul>
<%- for page in @pages_by_day[day] -%> <% for page in @pages_by_revision %>
<% if page.revised_on < revision_date %>
<% revision_date = page.revised_on %>
</ul>
<h3><%= revision_date.strftime('%B %e, %Y') %></h3>
<ul>
<% end %>
<li> <li>
<%= link_to_existing_page page %> <%= link_to_existing_page page %>
<%- if page.rev_ids.size > 1 %>
<span class="views">
( <%= link_to_revision(page, page.rev_ids.size, text='diff',
mode='diff') %> | <%= link_to_history(page, text='history') %> )
</span>
<%- end -%>
<div class="byline" style="margin-bottom: 0px"> <div class="byline" style="margin-bottom: 0px">
by <%= link_to_page(page.author) %> by <%= link_to_page(page.author) %>
at <%= format_date(page.revised_at) %> at <%= page.created_at.strftime "%H:%M" %>
<%= "from #{page.author.ip}" if page.author.respond_to?(:ip) %> <%= "from #{page.author.ip}" if page.author.respond_to?(:ip) %>
</div> </div>
</li> </li>
<%- end -%> <% end %>
</ul> </ul>
<%- end -%> <% end %>

View file

@ -1,31 +1,103 @@
<%- <% @title = "#{@page.plain_name} (Rev ##{@revision.number})" %>
@title = "#{@page.plain_name} (Rev ##{@revision_number}#{@show_diff ? ', changes' : ''})".html_safe
-%>
<div id="revision"> <div id="revision">
<%- if @show_diff -%> <%= @revision.display_content %>
<p class="show_diff"> </div>
Showing changes from revision #<%= @revision_number - 1 %> to #<%= @revision_number %>:
<ins class="diffins">Added</ins> | <del class="diffdel">Removed</del> | <del class="diffmod">Chan</del><ins class="diffmod">ged</ins> <div id="changes" style="display: none">
<p style="background: #eee; padding: 3px; border: 1px solid silver">
<small>
Showing changes from revision #<%= @revision.number - 1 %> to #<%= @revision.number %>:
<ins class="diffins">Added</ins> | <del class="diffdel">Removed</del>
</small>
</p> </p>
<%= @renderer.display_diff %>
<%- else -%> <%= @revision.display_diff %>
<%= @renderer.display_content %> </div>
<%- end -%>
</div> <!-- Revision -->
<div class="byline"> <div class="byline">
<%= "Revision from #{format_date(@revision.revised_at)} by" %> <%= "Revision from #{@revision.pretty_created_at} by" %>
<%= link_to_page @revision.author.purify %> <%= link_to_page @revision.author %>
</div> </div>
<div class="navigation navfoot"> <div class="navigation">
<%= raw navigation_menu_for_revision.join(' | ') %>
<span class="views"> <% if @revision.next_revision %>
| View: <% if @revision.next_revision.number < (@page.revisions.length - 1) %>
<%= link_to 'Source', {:web => @web.address, :action => 'source', :id => @page.name, :rev => @revision_number}, <%= link_to('Forward in time',
{:id => 'view_source', :rel => 'nofollow' } %> {:web => @web.address, :action => 'revision', :id => @page.name,
:rev => @revision.next_revision.number},
{:class => 'navlink', :name => 'to_next_revision'})
%>
<% else %>
<%= link_to('Forward in time',
{:web => @web.address, :action => 'show', :id => @page.name},
{:class => 'navlink', :name => 'to_next_revision'})
%>
<% end %>
<small>(<%= @revision.page.revisions.length - @revision.next_revision.number %> more)</small>
<% end %>
<% if @revision.next_revision && @revision.previous_revision %>
|
<% end %>
<% if @revision.previous_revision %>
<%= link_to('Back in time',
{:web => @web.address, :action => 'revision', :id => @page.name,
:rev => @revision.previous_revision.number},
{:class => 'navlink', :name => 'to_previous_revision'})
%>
<small>(<%= @revision.previous_revision.number + 1 %> more)</small>
<% end %>
|
<%= link_to('See current', {:web => @web.address, :action => 'show', :id => @page.name},
{:class => 'navlink', :name => 'to_current_revision'})
%>
<% if @revision.previous_revision %>
<span id="show_changes">
| <a href="#" onClick="toggleChanges(); return false;">See changes</a>
</span> </span>
<%= render :partial => 'inbound_links' %> <span id="hide_changes" style="display: none">
| <a href="#" onClick="toggleChanges(); return false;">Hide changes</a>
</span>
<% end %>
|
<%= link_to('Rollback',
{:web => @web.address, :action => 'rollback', :id => @page.name, :rev => @revision.number},
{:class => 'navlink', :name => 'rollback'})
%>
<% if @page.references.length > 0 %>
<small>
| Linked from:
<%= @page.references.collect { |ref|
link_to ref.name, :web => @web.address, :action => 'show', :id => ref.name
}.join(", ")
%>
</small>
<% else %>
Orphan page
<% end %>
</div> </div>
<script language="Javascript">
function toggleChanges() {
if (document.getElementById("changes").style.display == "none") {
document.getElementById("changes").style.display = "block";
document.getElementById("revision").style.display = "none";
document.getElementById("show_changes").style.display = "none";
document.getElementById("hide_changes").style.display = "inline";
} else {
document.getElementById("changes").style.display = "none";
document.getElementById("revision").style.display = "block";
document.getElementById("show_changes").style.display = "inline";
document.getElementById("hide_changes").style.display = "none";
}
}
</script>

View file

@ -1,34 +1,33 @@
<%- <%
@title = "Rollback to #{@page.plain_name} Rev ##{@revision_number}".html_safe @title = "Rollback to #{@page.plain_name} Rev ##{@revision.number}"
@content_width = 720 @content_width = 720
@hide_navigation = true @hide_navigation = true
-%> %>
<div id="MarkupHelp"> <%= "<p style='color:red'>Please correct the error that caused this error in rendering:<br/><small>#{@params["msg"]}</small></p>" if @params["msg"] %>
<%= render(:file => "#{@web.markup}_help") -%>
<%= render(:file => 'wiki_words_help') unless @web.brackets_only? -%>
</div>
<% form_tag({:web => @web.address, :action => 'save', :id => @page.name}, <%= form_tag({:web => @web.address, :action => 'save', :id => @page.name},
{ :id => 'editForm', :method => 'post', :onsubmit => 'cleanAuthorName();', {:id => 'editForm', :method => 'post', :onSubmit => 'cleanAuthorName();'})
'accept-charset' => 'utf-8' }) do %> %>
<div> <p>
<textarea name="content" id="content" rows="24" cols="60"><%= h(@revision.content.purify) %></textarea> <textarea name="content" style="font-size: 12px; width: 450px; height: 500px"><%= @revision.content %></textarea>
<div id="editFormButtons"> </p>
<p>
<input type="submit" value="Update" accesskey="u" /> as <input type="submit" value="Update" accesskey="u" /> as
<input type="text" name="author" id="authorName" value="<%= h(@author.purify) %>" <input type="text" name="author" id="authorName" value="<%= @author %>"
onclick="this.value == 'AnonymousCoward' ? this.value = '' : true" /> onClick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
| |
<span> <% link_to('Cancel',
<%= link_to('Cancel', {:web => @web.address, :action => 'cancel_edit', :id => @page.name}, {:web => @web.address, :action => 'cancel_edit', :id => @page.name},
{:accesskey => 'c'}) %> {:accesskey => 'c'})
<span class="unlock">(unlocks page)</span> %>
</span> <small>(unlocks page)</small>
</div> </p>
</div> <%= end_form_tag %>
<%- end -%>
<script type="text/javascript"> <%= render("#{@web.markup}_help") if @web %>
<script language="JavaScript1.2">
function cleanAuthorName() { function cleanAuthorName() {
if (document.getElementById('authorName').value == "") { if (document.getElementById('authorName').value == "") {
document.getElementById('authorName').value = 'AnonymousCoward'; document.getElementById('authorName').value = 'AnonymousCoward';

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title><%= @web.name %></title>
<link><%= url_for :only_path => false, :web => @web_name, :action => @link_action, :id => 'HomePage' %></link>
<description>An Instiki wiki</description>
<language>en-us</language>
<ttl>40</ttl>
<% for page in @pages_by_revision %>
<item>
<title><%= h page.plain_name %></title>
<% unless @hide_description %>
<description><%= h page.display_content %></description>
<% end %>
<pubDate><%= page.created_at.getgm.strftime "%a, %d %b %Y %H:%M:%S Z" %></pubDate>
<guid><%= url_for :only_path => false, :web => @web_name, :action => @link_action, :id => page.name %></guid>
<link><%= url_for :only_path => false, :web => @web_name, :action => @link_action, :id => page.name %></link>
<dc:creator><%= WikiWords.separate(page.author) %></dc:creator>
</item>
<% end %>
</channel>
</rss>

View file

@ -1,48 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg-flat.dtd" >
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><%=@page.plain_name%></title>
<!-- metadata -->
<meta name="generator" content="Instiki" />
<meta name="version" content="<%= "#{Instiki::VERSION::STRING}" %>" />
<meta name="author" content="<%=@page.author%>" />
<meta name="company" content="" />
<!-- configuration parameters -->
<meta name="defaultView" content="slideshow" />
<meta name="controlVis" content="hidden" />
<!-- style sheet links -->
<%= stylesheet_link_tag "/stylesheets/syntax.css", :media => 'all', :id => 'syntaxStyle' %>
<%= stylesheet_link_tag "/s5/ui/core/outline.css", :media => 'screen', :id => 'outlineStyle' %>
<%= stylesheet_link_tag "/s5/ui/core/print.css", :media => 'print', :id => 'slidePrint' %>
<%= stylesheet_link_tag "/s5/ui/core/opera.css", :media => 'projection', :id => 'operaFix' %>
<%= stylesheet_link_tag "/s5/ui/core/math.css", :media => 'all', :id => 'mathStyle' %>
<%= stylesheet_link_tag "/s5/themes/#{@s5_theme}/slides.css", :media => 'projection', :id => 'slideProj' %>
<!-- S5 JS -->
<%= javascript_include_tag 'prototype' %>
<%- if @web.markup == :markdownMML -%>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
MathML: { useMathMLspacing: true },
"HTML-CSS": { scale: 90 }
});
if (window._onload_fired_) MathJax.Hub.Startup.onload();
</script>
<script type="text/javascript">
if (!(Prototype.Browser.Gecko || navigator.userAgent.match(/MathPlayer/))) {
var s = document.createElement('script');
s.src = "<%= compute_public_path('MathJax.js', 'MathJax').split('?')[0] %>?config=MML_HTMLorMML";
document.querySelector('head').appendChild(s);
window.addEventListener("load", function(){window._onload_fired_ = true} , false);
};
</script>
<%- end -%>
<%= javascript_include_tag "/s5/ui/core/slides.js" %>
</head>
<body>
<%= @s5_content %>
</body>
</html>

View file

@ -1,41 +1,38 @@
<%- @title = "Search results for \"#{h @query}\"".html_safe -%> <% @title = "Search results for \"#{@params["query"]}\"" %>
<%- unless @title_results.empty? -%> <% unless @title_results.empty? %>
<h2><%= @title_results.length %> page(s) containing search string in the page name:</h2> <h2><%= @title_results.length %> page(s) containing search string in the page name:</h2>
<ul> <ul>
<%- for page in @title_results -%> <% for page in @title_results %>
<li> <li>
<%= link_to page.plain_name, :web => @web.address, :action => 'show', :id => page.name %> <%= link_to page.plain_name, :web => @web.address, :action => 'show', :id => page.name %>
</li> </li>
<%- end -%> <% end %>
</ul> </ul>
<%- end -%> <% end %>
<%- unless @results.empty? -%> <% unless @results.empty? %>
<h2> <%= @results.length %> page(s) containing search string in the page text:</h2> <h2> <%= @results.length %> page(s) containing search string in the page text:</h2>
<ul> <ul>
<%- for page in @results -%> <% for page in @results %>
<li> <li>
<%= link_to page.plain_name, :web => @web.address, :action => 'show', :id => page.name %> <%= link_to page.plain_name, :web => @web.address, :action => 'show', :id => page.name %>
</li> </li>
<%- end -%> <% end %>
</ul> </ul>
<%- end -%> <% end %>
<%- if (@results + @title_results).empty? -%> <% if (@results + @title_results).empty? %>
<h2>No pages contain "<%= h(@query).html_safe %>" </h2> <h2>No pages contain "<%= @params["query"] %>" </h2>
<p> <p>
Perhaps you should try expanding your query. Remember that Instiki searches for entire Perhaps you should try expanding your query. Remember that Instiki searches for entire
phrases, so if you search for "all that jazz" it will not match pages that contain these phrases, so if you search for "all that jazz" it will not match pages that contain these
words in separation &#x2014; only as a sentence fragment. words in separation&mdash;only as a sentence fragment.
</p> </p>
<p> <p>
If you're a high-tech computer wizard, you might even want try constructing a Ruby regular If you're a high-tech computer wizard, you might even want try constructing a Ruby regular
expression. That's actually what Instiki uses, so go right ahead and flex your expression. That's actually what Instiki uses, so go right ahead and flex your
"[a-z]*Leet?RegExpSkill(s|z)" "[a-z]*Leet?RegExpSkill(s|z)"
</p> </p>
<p> <% end %>
<b>Create a new page, named:</b> "<span class='newWikiWord'><%= link_to h(@query).html_safe, :web => @web.address, :action => 'new', :id => @query %></span>"
</p>
<%- end -%>

View file

@ -1,64 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg-flat.dtd" >
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>
<%- if @page and (@page.name == 'HomePage') -%>
<%= h(@web.name) %>
<%- else @web -%>
<%= @page.plain_name %> in <%= h @web.name %>
<%- end -%>
</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="robots" content="<%= @robots_metatag_value %>" />
<style type="text/css">
h1#pageName, div.info, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover, [actiontype="toggle"]:hover, #TextileHelp h3 {
color: #<%= @web ? @web.color : "393" %>;
}
<%= Rails.root.join('public', 'stylesheets', 'instiki.css').read if @inline_style %>
</style>
<%= stylesheet_link_tag 'instiki', :media => 'all' unless @inline_style %>
<%= "<style type='text/css'>#{@style_additions}</style>".html_safe if @style_additions %>
<style type="text/css"><!--/*--><![CDATA[/*><!--*/
<%= @web ? @web.additional_style : '' %>
/*]]>*/--></style>
<%= javascript_include_tag :defaults %>
<script type="text/javascript">
<!--//--><![CDATA[//><!--
function updateSize(elt, w, h) {
// adjust to the size of the user's browser area.
// w and h are the original, unadjusted, width and height per row/column
var parentheight = document.viewport.getHeight();
var parentwidth = $('Container').getWidth();
elt.writeAttribute({'cols': Math.floor(parentwidth/w) - 1,
'rows': Math.floor(parentheight/h) - 2 });
elt.setStyle({Width: parentwidth, Height: parentheight});
}
function resizeableTextarea() {
//make the textarea resize to fit available space
$$('textarea#content').each( function(textarea) {
var w = textarea.getWidth()/textarea.getAttribute('cols');
var h = textarea.getStyle('lineHeight').replace(/(\d*)px/, "$1");
Event.observe(window, 'resize', function(){ updateSize(textarea, w, h) });
updateSize(textarea, w, h);
Form.Element.focus(textarea);
});
}
window.onload = function (){
resizeableTextarea();
}
//--><!]]>
</script>
</head>
<body>
<div id="Container">
<textarea id='content' readonly=' readonly' rows='24' cols='60' ><%= (@revision ? @revision.content : @page.content).purify %></textarea>
</div> <!-- Container -->
</body>
</html>

View file

@ -1,3 +1,23 @@
\documentclass[12pt,titlepage]{article}
\usepackage[danish]{babel} %danske tekster
\usepackage[OT1]{fontenc} %rigtige danske bogstaver...
\usepackage{a4}
\usepackage{graphicx}
\usepackage{ucs}
\usepackage[utf8x]{inputenc}
\input epsf
%-------------------------------------------------------------------
\begin{document}
\sloppy
%-------------------------------------------------------------------
\section*{<%= @page.name %>} \section*{<%= @page.name %>}
<%= @tex_content.html_safe %> <%= @tex_content %>
\end{document}

View file

@ -1 +0,0 @@
<%= @tex_content.html_safe %>

View file

@ -1,11 +1,19 @@
\documentclass[12pt,titlepage]{article} \documentclass[12pt,titlepage]{article}
\usepackage{amsmath} \usepackage{fancyhdr}
\usepackage{amsfonts} \pagestyle{fancy}
\fancyhead[LE,RO]{}
\fancyhead[LO,RE]{\nouppercase{\bfseries \leftmark}}
\fancyfoot[C]{\thepage}
\usepackage[danish]{babel} %danske tekster
\usepackage{a4}
\usepackage{graphicx} \usepackage{graphicx}
\usepackage{ucs} \usepackage{ucs}
\usepackage[utf8x]{inputenc} \usepackage[utf8]{inputenc}
\usepackage{hyperref} \input epsf
%------------------------------------------------------------------- %-------------------------------------------------------------------
@ -18,6 +26,8 @@
\tableofcontents \tableofcontents
\pagebreak \pagebreak
\sloppy
%------------------------------------------------------------------- %-------------------------------------------------------------------
<%= @tex_content %> <%= @tex_content %>

View file

@ -1,28 +1,18 @@
<%- @title = "Wiki webs" -%> <% @title = "Wiki webs" %>
<ul> <ul>
<%- @webs.each do |web| -%> <% @webs.each do |web| %>
<li> <li>
<%- if web.password -%> <% if web.published %>
<div class="web_protected"> <%= link_to_page 'HomePage', web, web.name, :mode => 'publish' %>
<%- else -%> (read-only) /
<div class="web_normal"> <%= link_to_page 'HomePage', web, 'editable version', :mode => 'show' %> (requires login)
<%- end -%> <% else %>
<%= link_to(web.name, {:web => web.address, :action => 'show', :id => 'HomePage'}) %> <%= link_to_page 'HomePage', web, web.name, :mode => 'show' %>
<%- if web.published? -%> <% end %>
(<%= link_to_page 'HomePage', web, 'published version', :mode => 'publish' %>)
<%- end -%>
<div class="byline" style="margin-bottom: 0px"> <div class="byline" style="margin-bottom: 0px">
<%= web.pages.size %> page<% if web.pages.size != 1 %>s<% end %> by <%= web.authors.size %> author<% if web.authors.size != 1 %>s<% end %> <%= web.pages.length %> pages by <%= web.authors.length %> authors
- Last Update: <%= web.last_page.nil? ? format_date(web.created_at) : format_date(web.last_page.revised_at) %><br/>
<%- if ! web.last_page.nil? -%>
Last Document: <%= link_to_page(web.last_page.name,web) %>
<%= web.last_page.revisions? ? "Revised" : "Created" %> by <%= author_link(web.last_page) %> (<%= web.last_page.current_revision.ip %>)
<%- end -%>
</div>
</div> </div>
</li> </li>
<%- end -%> <% end %>
</ul> </ul>

View file

@ -1,530 +0,0 @@
require 'strscan'
module XHTML #:nodoc:
class Conditions < Hash #:nodoc:
def initialize(hash)
super()
hash = { :content => hash } unless Hash === hash
hash = keys_to_symbols(hash)
hash.each do |k,v|
case k
when :tag, :content then
# keys are valid, and require no further processing
when :attributes then
hash[k] = keys_to_strings(v)
when :parent, :child, :ancestor, :descendant, :sibling, :before,
:after
hash[k] = Conditions.new(v)
when :children
hash[k] = v = keys_to_symbols(v)
v.each do |k,v2|
case k
when :count, :greater_than, :less_than
# keys are valid, and require no further processing
when :only
v[k] = Conditions.new(v2)
else
raise "illegal key #{k.inspect} => #{v2.inspect}"
end
end
else
raise "illegal key #{k.inspect} => #{v.inspect}"
end
end
update hash
end
private
def keys_to_strings(hash)
hash.keys.inject({}) do |h,k|
h[k.to_s] = hash[k]
h
end
end
def keys_to_symbols(hash)
hash.keys.inject({}) do |h,k|
raise "illegal key #{k.inspect}" unless k.respond_to?(:to_sym)
h[k.to_sym] = hash[k]
h
end
end
end
# The base class of all nodes, textual and otherwise, in an HTML document.
class Node #:nodoc:
# The array of children of this node. Not all nodes have children.
attr_reader :children
# The parent node of this node. All nodes have a parent, except for the
# root node.
attr_reader :parent
# The line number of the input where this node was begun
attr_reader :line
# The byte position in the input where this node was begun
attr_reader :position
# Create a new node as a child of the given parent.
def initialize(parent, line=0, pos=0)
@parent = parent
@children = []
@line, @position = line, pos
end
# Return a textual representation of the node.
def to_s
s = ""
@children.each { |child| s << child.to_s }
s
end
# Return false (subclasses must override this to provide specific matching
# behavior.) +conditions+ may be of any type.
def match(conditions)
false
end
# Search the children of this node for the first node for which #find
# returns non +nil+. Returns the result of the #find call that succeeded.
def find(conditions)
conditions = validate_conditions(conditions)
@children.each do |child|
node = child.find(conditions)
return node if node
end
nil
end
# Search for all nodes that match the given conditions, and return them
# as an array.
def find_all(conditions)
conditions = validate_conditions(conditions)
matches = []
matches << self if match(conditions)
@children.each do |child|
matches.concat child.find_all(conditions)
end
matches
end
# Returns +false+. Subclasses may override this if they define a kind of
# tag.
def tag?
false
end
def validate_conditions(conditions)
Conditions === conditions ? conditions : Conditions.new(conditions)
end
def ==(node)
return false unless self.class == node.class && children.size == node.children.size
equivalent = true
children.size.times do |i|
equivalent &&= children[i] == node.children[i]
end
equivalent
end
class <<self
def parse(parent, line, pos, content, strict=true)
if content !~ /^<\S/
Text.new(parent, line, pos, content)
else
scanner = StringScanner.new(content)
unless scanner.skip(/</)
if strict
raise "expected <"
else
return Text.new(parent, line, pos, content)
end
end
if scanner.skip(/!\[CDATA\[/)
scanner.scan_until(/\]\]>/)
return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, ''))
end
closing = ( scanner.scan(/\//) ? :close : nil )
return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:-]+/)
name
unless closing
scanner.skip(/\s*/)
attributes = {}
while attr = scanner.scan(/[-\w:]+/)
value = true
if scanner.scan(/\s*=\s*/)
if delim = scanner.scan(/['"]/)
value = ""
while text = scanner.scan(/[^#{delim}\\]+|./)
case text
when "\\" then
value << text
value << scanner.getch
when delim
break
else value << text
end
end
else
value = scanner.scan(/[^\s>\/]+/)
end
end
attributes[attr] = value
scanner.skip(/\s*/)
end
closing = ( scanner.scan(/\//) ? :self : nil )
end
unless scanner.scan(/\s*>/)
if strict
raise "expected > (got #{scanner.rest.inspect} for #{content}, #{attributes.inspect})"
else
# throw away all text until we find what we're looking for
scanner.skip_until(/>/) or scanner.terminate
end
end
Tag.new(parent, line, pos, name, attributes, closing)
end
end
end
end
# A node that represents text, rather than markup.
class Text < Node #:nodoc:
attr_reader :content
# Creates a new text node as a child of the given parent, with the given
# content.
def initialize(parent, line, pos, content)
super(parent, line, pos)
@content = content
end
# Returns the content of this node.
def to_s
@content
end
# Returns +self+ if this node meets the given conditions. Text nodes support
# conditions of the following kinds:
#
# * if +conditions+ is a string, it must be a substring of the node's
# content
# * if +conditions+ is a regular expression, it must match the node's
# content
# * if +conditions+ is a hash, it must contain a <tt>:content</tt> key that
# is either a string or a regexp, and which is interpreted as described
# above.
def find(conditions)
match(conditions) && self
end
# Returns non-+nil+ if this node meets the given conditions, or +nil+
# otherwise. See the discussion of #find for the valid conditions.
def match(conditions)
case conditions
when String
@content == conditions
when Regexp
@content =~ conditions
when Hash
conditions = validate_conditions(conditions)
# Text nodes only have :content, :parent, :ancestor
unless (conditions.keys - [:content, :parent, :ancestor]).empty?
return false
end
match(conditions[:content])
else
nil
end
end
def ==(node)
return false unless super
content == node.content
end
end
# A CDATA node is simply a text node with a specialized way of displaying
# itself.
class CDATA < Text #:nodoc:
def to_s
"<![CDATA[#{super}]>"
end
end
# A Tag is any node that represents markup. It may be an opening tag, a
# closing tag, or a self-closing tag. It has a name, and may have a hash of
# attributes.
class Tag < Node #:nodoc:
# Either +nil+, <tt>:close</tt>, or <tt>:self</tt>
attr_reader :closing
# Either +nil+, or a hash of attributes for this node.
attr_reader :attributes
# The name of this tag.
attr_reader :name
# Create a new node as a child of the given parent, using the given content
# to describe the node. It will be parsed and the node name, attributes and
# closing status extracted.
def initialize(parent, line, pos, name, attributes, closing)
super(parent, line, pos)
@name = name
@attributes = attributes
@closing = closing
end
# A convenience for obtaining an attribute of the node. Returns +nil+ if
# the node has no attributes.
def [](attr)
@attributes ? @attributes[attr] : nil
end
# Returns non-+nil+ if this tag can contain child nodes.
def childless?(xml = false)
return false if xml && @closing.nil?
!@closing.nil? ||
@name =~ /^(img|br|hr|link|meta|area|base|basefont|
col|frame|input|isindex|param)$/ox
end
# Returns a textual representation of the node
def to_s
if @closing == :close
"</#{@name}>"
else
s = "<#{@name}"
@attributes.each do |k,v|
s << " #{k}"
s << "=\"#{v}\"" if String === v
end
s << " /" if @closing == :self
s << ">"
@children.each { |child| s << child.to_s }
s << "</#{@name}>" if @closing != :self && !@children.empty?
s
end
end
# If either the node or any of its children meet the given conditions, the
# matching node is returned. Otherwise, +nil+ is returned. (See the
# description of the valid conditions in the +match+ method.)
def find(conditions)
match(conditions) && self || super
end
# Returns +true+, indicating that this node represents an HTML tag.
def tag?
true
end
# Returns +true+ if the node meets any of the given conditions. The
# +conditions+ parameter must be a hash of any of the following keys
# (all are optional):
#
# * <tt>:tag</tt>: the node name must match the corresponding value
# * <tt>:attributes</tt>: a hash. The node's values must match the
# corresponding values in the hash.
# * <tt>:parent</tt>: a hash. The node's parent must match the
# corresponding hash.
# * <tt>:child</tt>: a hash. At least one of the node's immediate children
# must meet the criteria described by the hash.
# * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
# meet the criteria described by the hash.
# * <tt>:descendant</tt>: a hash. At least one of the node's descendants
# must meet the criteria described by the hash.
# * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
# meet the criteria described by the hash.
# * <tt>:after</tt>: a hash. The node must be after any sibling meeting
# the criteria described by the hash, and at least one sibling must match.
# * <tt>:before</tt>: a hash. The node must be before any sibling meeting
# the criteria described by the hash, and at least one sibling must match.
# * <tt>:children</tt>: a hash, for counting children of a node. Accepts the
# keys:
# ** <tt>:count</tt>: either a number or a range which must equal (or
# include) the number of children that match.
# ** <tt>:less_than</tt>: the number of matching children must be less than
# this number.
# ** <tt>:greater_than</tt>: the number of matching children must be
# greater than this number.
# ** <tt>:only</tt>: another hash consisting of the keys to use
# to match on the children, and only matching children will be
# counted.
#
# Conditions are matched using the following algorithm:
#
# * if the condition is a string, it must be a substring of the value.
# * if the condition is a regexp, it must match the value.
# * if the condition is a number, the value must match number.to_s.
# * if the condition is +true+, the value must not be +nil+.
# * if the condition is +false+ or +nil+, the value must be +nil+.
#
# Usage:
#
# # test if the node is a "span" tag
# node.match :tag => "span"
#
# # test if the node's parent is a "div"
# node.match :parent => { :tag => "div" }
#
# # test if any of the node's ancestors are "table" tags
# node.match :ancestor => { :tag => "table" }
#
# # test if any of the node's immediate children are "em" tags
# node.match :child => { :tag => "em" }
#
# # test if any of the node's descendants are "strong" tags
# node.match :descendant => { :tag => "strong" }
#
# # test if the node has between 2 and 4 span tags as immediate children
# node.match :children => { :count => 2..4, :only => { :tag => "span" } }
#
# # get funky: test to see if the node is a "div", has a "ul" ancestor
# # and an "li" parent (with "class" = "enum"), and whether or not it has
# # a "span" descendant that contains # text matching /hello world/:
# node.match :tag => "div",
# :ancestor => { :tag => "ul" },
# :parent => { :tag => "li",
# :attributes => { :class => "enum" } },
# :descendant => { :tag => "span",
# :child => /hello world/ }
def match(conditions)
conditions = validate_conditions(conditions)
# check content of child nodes
if conditions[:content]
if children.empty?
return false unless match_condition("", conditions[:content])
else
return false unless children.find { |child| child.match(conditions[:content]) }
end
end
# test the name
return false unless match_condition(@name, conditions[:tag]) if conditions[:tag]
# test attributes
(conditions[:attributes] || {}).each do |key, value|
return false unless match_condition(self[key], value)
end
# test parent
return false unless parent.match(conditions[:parent]) if conditions[:parent]
# test children
return false unless children.find { |child| child.match(conditions[:child]) } if conditions[:child]
# test ancestors
if conditions[:ancestor]
return false unless catch :found do
p = self
throw :found, true if p.match(conditions[:ancestor]) while p = p.parent
end
end
# test descendants
if conditions[:descendant]
return false unless children.find do |child|
# test the child
child.match(conditions[:descendant]) ||
# test the child's descendants
child.match(:descendant => conditions[:descendant])
end
end
# count children
if opts = conditions[:children]
matches = children.select do |c|
(c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?))
end
matches = matches.select { |c| c.match(opts[:only]) } if opts[:only]
opts.each do |key, value|
next if key == :only
case key
when :count
if Integer === value
return false if matches.length != value
else
return false unless value.include?(matches.length)
end
when :less_than
return false unless matches.length < value
when :greater_than
return false unless matches.length > value
else raise "unknown count condition #{key}"
end
end
end
# test siblings
if conditions[:sibling] || conditions[:before] || conditions[:after]
siblings = parent ? parent.children : []
self_index = siblings.index(self)
if conditions[:sibling]
return false unless siblings.detect do |s|
s != self && s.match(conditions[:sibling])
end
end
if conditions[:before]
return false unless siblings[self_index+1..-1].detect do |s|
s != self && s.match(conditions[:before])
end
end
if conditions[:after]
return false unless siblings[0,self_index].detect do |s|
s != self && s.match(conditions[:after])
end
end
end
true
end
def ==(node)
return false unless super
return false unless closing == node.closing && self.name == node.name
attributes == node.attributes
end
private
# Match the given value to the given condition.
def match_condition(value, condition)
case condition
when String
value && value == condition
when Regexp
value && value.match(condition)
when Numeric
value == condition.to_s
when true
!value.nil?
when false, nil
value.nil?
else
false
end
end
end
end

View file

@ -1,262 +0,0 @@
# == Introduction
#
# This module provides sanitization of XHTML+MathML+SVG
# and of inline style attributes. Its genesis is {described here}[http://golem.ph.utexas.edu/~distler/blog/archives/001181.html].
#
# Uses the {HTML5lib parser}[http://code.google.com/p/html5lib/], so that the parsing behaviour should
# resemble that of browsers.
#
# sanitize_xhtml() is a case-sensitive sanitizer, suitable for XHTML
# sanitize_html() is a case-insensitive sanitizer suitable for HTML
# sanitize_rexml() sanitizes a REXML tree, returning a string
# safe_sanitize_xhtml() makes extra-sure that the result is well-formed XHTML
# by running the output of sanitize_xhtml() through REXML
#
# == Files
#
# {sanitize.rb}[http://golem.ph.utexas.edu/~distler/code/instiki/svn/lib/sanitize.rb],
# {HTML5lib}[http://golem.ph.utexas.edu/~distler/code/instiki/svn/vendor/plugins/HTML5lib/]
#
# == Author
#
# {Jacques Distler}[http://golem.ph.utexas.edu/~distler/]
#
# == License
#
# Ruby License
module Sanitize
require 'html5/html5parser'
require 'html5/liberalxmlparser'
require 'html5/treewalkers'
require 'html5/treebuilders'
require 'html5/serializer'
require 'html5/sanitizer'
require 'stringsupport.rb'
include HTML5
# Sanitize a string, parsed using XHTML parsing rules.
#
# :call-seq:
# sanitize_xhtml(string) -> string
# sanitize_xhtml(string, {:encoding => 'iso-8859-1', :to_tree => true}) -> REXML::Document
#
# Unless otherwise specified, the string is assumed to be utf-8 encoded.
# By default, the output is a string. But, optionally, you can return a REXML tree.
#
# The string returned is utf-8 encoded. If you want, you can use iconv to convert it to some other encoding.
# (REXML trees are always utf-8 encoded.)
def sanitize_xhtml(html, options = {})
@encoding = 'utf-8'
@treebuilder = TreeBuilders::REXML::TreeBuilder
@to_tree = false
options.each do |name, value|
next unless %w(encoding treebuilder to_tree).include? name.to_s
if name.to_s == 'treebuilder'
@treebuilder = HTML5lib::TreeBuilders.get_tree_builder(value)
else
instance_variable_set("@#{name}", value)
end
end
if @encoding == 'utf-8'
parsed = XHTMLParser.parse_fragment(html.to_utf8, {:tokenizer => HTMLSanitizer,
:lowercase_element_name => false, :lowercase_attr_name => false,
:encoding => @encoding, :tree => @treebuilder })
else
parsed = XHTMLParser.parse_fragment(html.to_ncr, {:tokenizer => HTMLSanitizer,
:lowercase_element_name => false, :lowercase_attr_name => false,
:encoding => @encoding, :tree => @treebuilder })
end
return parsed if @to_tree
return parsed.to_s
end
# Sanitize a string, parsed using XHTML parsing rules. Reparse the result to
# ensure well-formedness.
#
# :call-seq:
# safe_sanitize_xhtml(string) -> string
#
# Unless otherwise specified, the string is assumed to be utf-8 encoded.
#
# The string returned is utf-8 encoded. If you want, you can use iconv to convert it to some other encoding.
# (REXML trees are always utf-8 encoded.)
def safe_sanitize_xhtml(html, options = {})
options[:to_tree] = false
sanitized = sanitize_xhtml(html, options)
doc = REXML::Document.new("<div xmlns='http://www.w3.org/1999/xhtml'>#{sanitized}</div>")
sanitized = doc.to_s.gsub(/\A<div xmlns='http:\/\/www.w3.org\/1999\/xhtml'>(.*)<\/div>\Z/m, '\1')
rescue REXML::ParseException
sanitized = sanitized.escapeHTML
end
# Sanitize a string, parsed using HTML parsing rules.
#
# :call-seq:
# sanitize_html( string ) -> string
# sanitize_html( string, {:encoding => 'iso-8859-1', :to_tree => true} ) -> REXML::Document
#
# Unless otherwise specified, the string is assumed to be utf-8 encoded.
# By default, the output is a string. But, optionally, you can return a REXML tree.
#
# The string returned is utf-8 encoded. If you want, you can use iconv to convert it to some other encoding.
# (REXML trees are always utf-8 encoded.)
def sanitize_html(html, options = {})
@encoding = 'utf-8'
@treebuilder = TreeBuilders::REXML::TreeBuilder
@to_tree = false
options.each do |name, value|
next unless %w(encoding treebuilder to_tree).include? name.to_s
if name.to_s == 'treebuilder'
@treebuilder = HTML5lib::TreeBuilders.get_tree_builder(value)
else
instance_variable_set("@#{name}", value)
end
end
if @encoding == 'utf-8'
parsed = HTMLParser.parse_fragment(html.to_utf8, {:tokenizer => HTMLSanitizer,
:encoding => @encoding, :tree => @treebuilder })
else
parsed = HTMLParser.parse_fragment(html.to_ncr, {:tokenizer => HTMLSanitizer,
:encoding => @encoding, :tree => @treebuilder })
end
return parsed if @to_tree
return parsed.to_s
end
# Sanitize a REXML tree. The output is a string.
#
# :call-seq:
# sanitize_rexml(tree) -> string
#
def sanitize_rexml(tree)
tokens = TreeWalkers.get_tree_walker('rexml2').new(tree)
XHTMLSerializer.serialize(tokens, {:encoding=>'utf-8',
:space_before_trailing_solidus => true,
:inject_meta_charset => false,
:sanitize => true})
end
end
require 'rexml/element'
module REXML #:nodoc:
class Element
# Convert XHTML+MathML Named Entities in a REXML::Element to Numeric Character References
#
# :call-seq:
# tree.to_ncr -> REXML::Element
#
# REXML, typically, converts NCRs to utf-8 characters, which is what you'll see when you
# access the resulting REXML document.
#
# Note that this method needs to traverse the entire tree, converting text nodes and attributes
# for each element. This can be SLOW. It will often be faster to serialize to a string and then
# use String.to_ncr instead.
#
def to_ncr
self.each_element { |el|
el.texts.each_index {|i|
el.texts[i].value = el.texts[i].to_s.to_ncr
}
el.attributes.each { |name,val|
el.attributes[name] = val.to_ncr
}
el.to_ncr if el.has_elements?
}
return self
end
# Convert XHTML+MathML Named Entities in a REXML::Element to UTF-8
#
# :call-seq:
# tree.to_utf8 -> REXML::Element
#
# Note that this method needs to traverse the entire tree, converting text nodes and attributes
# for each element. This can be SLOW. It will often be faster to serialize to a string and then
# use String.to_utf8 instead.
#
def to_utf8
self.each_element { |el|
el.texts.each_index {|i|
el.texts[i].value = el.texts[i].to_s.to_utf8
}
el.attributes.each { |name,val|
el.attributes[name] = val.to_utf8
}
el.to_utf8 if el.has_elements?
}
return self
end
end
end
module HTML5 #:nodoc: all
module TreeWalkers
private
class << self
def [](name)
case name.to_s.downcase
when 'rexml'
require 'html5/treewalkers/rexml'
REXML::TreeWalker
when 'rexml2'
REXML2::TreeWalker
else
raise "Unknown TreeWalker #{name}"
end
end
alias :get_tree_walker :[]
end
module REXML2
class TreeWalker < HTML5::TreeWalkers::NonRecursiveTreeWalker
private
def node_details(node)
case node
when ::REXML::Document
[:DOCUMENT]
when ::REXML::Element
if !node.name
[:DOCUMENT_FRAGMENT]
else
[:ELEMENT, node.name,
node.attributes.map {|name,value| [name,value.to_utf8]},
node.has_elements? || node.has_text?]
end
when ::REXML::Text
[:TEXT, node.value.to_utf8]
when ::REXML::Comment
[:COMMENT, node.string]
when ::REXML::DocType
[:DOCTYPE, node.name, node.public, node.system]
when ::REXML::XMLDecl
[nil]
else
[:UNKNOWN, node.class.inspect]
end
end
def first_child(node)
node.children.first
end
def next_sibling(node)
node.next_sibling
end
def parent(node)
node.parent
end
end
end
end
end

View file

@ -1,189 +0,0 @@
#!/usr/bin/env ruby
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
require 'sanitize'
require 'json'
class SanitizeTest < Test::Unit::TestCase
include Sanitize
def setup
end
def do_sanitize_xhtml stream
safe_sanitize_xhtml(stream)
end
def check_sanitization(input, htmloutput, xhtmloutput, rexmloutput)
assert_equal htmloutput, do_sanitize_xhtml(input)
end
def rexml_doc(string)
REXML::Document.new(
"<div xmlns='http://www.w3.org/1999/xhtml'>#{string}</div>")
end
def my_rex(string)
sanitize_rexml(rexml_doc(string.to_utf8)).gsub(/\A<div xmlns="http:\/\/www.w3.org\/1999\/xhtml">(.*)<\/div>\Z/m, '\1')
end
def test_sanitize_named_entities
input = '<p>Greek &phis; &phi;, double-struck &Aopf;, numeric &#x1D538; &#8279;, uppercase &TRADE; &LT;</p>'
output = "<p>Greek \317\225 \317\206, double-struck \360\235\224\270, numeric \360\235\224\270 \342\201\227, uppercase \342\204\242 &lt;</p>"
output2 = "<p>Greek \317\225 \317\206, double-struck \360\235\224\270, numeric &#x1D538; &#8279;, uppercase \342\204\242 &lt;</p>"
assert_equal(output, sanitize_xhtml(input))
assert_equal(output, sanitize_html(input))
assert_equal(output, my_rex(input))
assert_equal(output2, input.to_utf8)
end
def test_sanitize_malformed_utf8
input = "<p>\357elephant &AMP; \302ivory</p>"
output = "<p>\357\277\275elephant &amp; \357\277\275ivory</p>"
check_sanitization(input, output, output, output)
end
Sanitizer::ALLOWED_ELEMENTS.each do |tag_name|
define_method "test_should_allow_#{tag_name}_tag" do
input = "<#{tag_name} title='1'>foo <bad>bar</bad> baz</#{tag_name}>"
htmloutput = "<#{tag_name.downcase} title='1'>foo &lt;bad&gt;bar&lt;/bad&gt; baz</#{tag_name.downcase}>"
xhtmloutput = "<#{tag_name} title='1'>foo &lt;bad&gt;bar&lt;/bad&gt; baz</#{tag_name}>"
rexmloutput = xhtmloutput
if %w[caption colgroup optgroup option tbody td tfoot th thead tr].include?(tag_name)
htmloutput = "foo &lt;bad&gt;bar&lt;/bad&gt; baz"
xhtmloutput = htmloutput
elsif tag_name == 'col'
htmloutput = "foo &lt;bad&gt;bar&lt;/bad&gt; baz"
xhtmloutput = htmloutput
rexmloutput = "<col title='1' />"
elsif tag_name == 'table'
htmloutput = "foo &lt;bad&gt;bar&lt;/bad&gt;baz<table title='1'> </table>"
xhtmloutput = htmloutput
elsif tag_name == 'image'
htmloutput = "<img title='1'/>foo &lt;bad&gt;bar&lt;/bad&gt; baz"
xhtmloutput = htmloutput
rexmloutput = "<image title='1'>foo &lt;bad&gt;bar&lt;/bad&gt; baz</image>"
elsif VOID_ELEMENTS.include?(tag_name)
htmloutput = "<#{tag_name} title='1'/>foo &lt;bad&gt;bar&lt;/bad&gt; baz"
xhtmloutput = htmloutput
htmloutput += '<br/>' if tag_name == 'br'
rexmloutput = "<#{tag_name} title='1' />"
end
check_sanitization(input, xhtmloutput, xhtmloutput, rexmloutput)
end
end
Sanitizer::ALLOWED_ELEMENTS.each do |tag_name|
define_method "test_should_forbid_#{tag_name.upcase}_tag" do
input = "<#{tag_name.upcase} title='1'>foo <bad>bar</bad> baz</#{tag_name.upcase}>"
output = "&lt;#{tag_name.upcase} title=\"1\"&gt;foo &lt;bad&gt;bar&lt;/bad&gt; baz&lt;/#{tag_name.upcase}&gt;"
xhtmloutput = "&lt;#{tag_name.upcase} title='1'&gt;foo &lt;bad&gt;bar&lt;/bad&gt; baz&lt;/#{tag_name.upcase}&gt;"
check_sanitization(input, output, xhtmloutput, output)
end
end
Sanitizer::ALLOWED_ATTRIBUTES.each do |attribute_name|
next if attribute_name == 'style' || attribute_name.include?(':')
define_method "test_should_allow_#{attribute_name}_attribute" do
input = "<p #{attribute_name}='foo'>foo <bad>bar</bad> baz</p>"
output = "<p #{attribute_name}='foo'>foo &lt;bad&gt;bar&lt;/bad&gt; baz</p>"
htmloutput = "<p #{attribute_name.downcase}='foo'>foo &lt;bad&gt;bar&lt;/bad&gt; baz</p>"
check_sanitization(input, output, output, output)
end
end
Sanitizer::ALLOWED_ATTRIBUTES.each do |attribute_name|
define_method "test_should_forbid_#{attribute_name.upcase}_attribute" do
input = "<p #{attribute_name.upcase}='display: none;'>foo <bad>bar</bad> baz</p>"
output = "<p>foo &lt;bad&gt;bar&lt;/bad&gt; baz</p>"
check_sanitization(input, output, output, output)
end
end
Sanitizer::ALLOWED_PROTOCOLS.each do |protocol|
define_method "test_should_allow_#{protocol}_uris" do
input = %(<a href="#{protocol}">foo</a>)
output = "<a href='#{protocol}'>foo</a>"
check_sanitization(input, output, output, output)
end
end
Sanitizer::ALLOWED_PROTOCOLS.each do |protocol|
define_method "test_should_allow_uppercase_#{protocol}_uris" do
input = %(<a href="#{protocol.upcase}">foo</a>)
output = "<a href='#{protocol.upcase}'>foo</a>"
check_sanitization(input, output, output, output)
end
end
Sanitizer::SVG_ALLOW_LOCAL_HREF.each do |tag_name|
next unless Sanitizer::ALLOWED_ELEMENTS.include?(tag_name)
define_method "test_#{tag_name}_should_allow_local_href_with_ns_decl" do
input = %(<#{tag_name} xlink:href="#foo" xmlns:xlink='http://www.w3.org/1999/xlink'/>)
output = "<#{tag_name.downcase} xlink:href='#foo' xmlns:xlink='http://www.w3.org/1999/xlink'/>"
xhtmloutput = "<#{tag_name} xlink:href='#foo' xmlns:xlink='http://www.w3.org/1999/xlink'/>"
check_sanitization(input, xhtmloutput, xhtmloutput, xhtmloutput)
end
define_method "test_#{tag_name}_should_allow_local_href_with_newline_and_ns_decl" do
input = %(<#{tag_name} xlink:href="\n#foo" xmlns:xlink='http://www.w3.org/1999/xlink'/>)
output = "<#{tag_name.downcase} xlink:href='\n#foo' xmlns:xlink='http://www.w3.org/1999/xlink'/>"
xhtmloutput = "<#{tag_name} xlink:href='\n#foo' xmlns:xlink='http://www.w3.org/1999/xlink'/>"
check_sanitization(input, xhtmloutput, xhtmloutput, xhtmloutput)
end
define_method "test_#{tag_name}_should_forbid_local_href_without_ns_decl" do
input = %(<#{tag_name} xlink:href="#foo"/>)
output = "&lt;#{tag_name.downcase} xlink:href='#foo'/>"
xhtmloutput = "&lt;#{tag_name} xlink:href=&#39;#foo&#39;&gt;&lt;/#{tag_name}&gt;"
check_sanitization(input, xhtmloutput, xhtmloutput, xhtmloutput)
end
define_method "test_#{tag_name}_should_forbid_local_href_with_newline_without_ns_decl" do
input = %(<#{tag_name} xlink:href="\n#foo"/>)
output = "&lt;#{tag_name.downcase} xlink:href='\n#foo'/>"
xhtmloutput = "&lt;#{tag_name} xlink:href=&#39;\n#foo&#39;&gt;&lt;/#{tag_name}&gt;"
check_sanitization(input, xhtmloutput, xhtmloutput, xhtmloutput)
end
define_method "test_#{tag_name}_should_forbid_nonlocal_href_with_ns_decl" do
input = %(<#{tag_name} xlink:href="http://bad.com/foo" xmlns:xlink='http://www.w3.org/1999/xlink'/>)
output = "<#{tag_name.downcase} xmlns:xlink='http://www.w3.org/1999/xlink'/>"
xhtmloutput = "<#{tag_name} xmlns:xlink='http://www.w3.org/1999/xlink'/>"
check_sanitization(input, xhtmloutput, xhtmloutput, xhtmloutput)
end
define_method "test_#{tag_name}_should_forbid_nonlocal_href_with_newline_and_ns_decl" do
input = %(<#{tag_name} xlink:href="\nhttp://bad.com/foo" xmlns:xlink='http://www.w3.org/1999/xlink'/>)
output = "<#{tag_name.downcase} xmlns:xlink='http://www.w3.org/1999/xlink'/>"
xhtmloutput = "<#{tag_name} xmlns:xlink='http://www.w3.org/1999/xlink'/>"
check_sanitization(input, xhtmloutput, xhtmloutput, xhtmloutput)
end
end
def test_should_handle_astral_plane_characters
input = "<p>&#x1d4b5; &#x1d538;</p>"
output = "<p>\360\235\222\265 \360\235\224\270</p>"
check_sanitization(input, output, output, output)
input = "<p><tspan>\360\235\224\270</tspan> a</p>"
output = "<p><tspan>\360\235\224\270</tspan> a</p>"
check_sanitization(input, output, output, output)
end
JSON::parse(open(File.expand_path(File.join(File.dirname(__FILE__), '/../sanitizer.dat'))).read).each do |test|
define_method "test_#{test['name']}" do
check_sanitization(
test['input'],
test['output'],
test['xhtml'] || test['output'],
test['rexml'] || test['output']
)
end
end
end

View file

@ -1,10 +0,0 @@
== 0.10.0 2007-10-08
* proof-of-concept validator
* easier to localize error reporting
* many unit tests
== 0.1.0 / 2007-08-07
* 1 major enhancement
* Birthday!

View file

@ -1,17 +0,0 @@
Copyright (c) 2006-2007 The Authors
Contributers:
James Graham - jg307@cam.ac.uk
Anne van Kesteren - annevankesteren@gmail.com
Lachlan Hunt - lachlan.hunt@lachy.id.au
Matt McDonald - kanashii@kanashii.ca
Sam Ruby - rubys@intertwingly.net
Ian Hickson (Google) - ian@hixie.ch
Thomas Broyer - t.broyer@ltgt.net
Jacques Distler - distler@golem.ph.utexas.edu
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,116 +0,0 @@
History.txt
Manifest.txt
README
Rakefile.rb
bin/html5
lib/html5.rb
lib/html5/constants.rb
lib/html5/filters/base.rb
lib/html5/filters/inject_meta_charset.rb
lib/html5/filters/iso639codes.rb
lib/html5/filters/optionaltags.rb
lib/html5/filters/rfc2046.rb
lib/html5/filters/rfc3987.rb
lib/html5/filters/sanitizer.rb
lib/html5/filters/validator.rb
lib/html5/filters/whitespace.rb
lib/html5/html5parser.rb
lib/html5/html5parser/after_body_phase.rb
lib/html5/html5parser/after_frameset_phase.rb
lib/html5/html5parser/after_head_phase.rb
lib/html5/html5parser/before_head_phase.rb
lib/html5/html5parser/in_body_phase.rb
lib/html5/html5parser/in_caption_phase.rb
lib/html5/html5parser/in_cell_phase.rb
lib/html5/html5parser/in_column_group_phase.rb
lib/html5/html5parser/in_frameset_phase.rb
lib/html5/html5parser/in_head_phase.rb
lib/html5/html5parser/in_row_phase.rb
lib/html5/html5parser/in_select_phase.rb
lib/html5/html5parser/in_table_body_phase.rb
lib/html5/html5parser/in_table_phase.rb
lib/html5/html5parser/initial_phase.rb
lib/html5/html5parser/phase.rb
lib/html5/html5parser/root_element_phase.rb
lib/html5/html5parser/trailing_end_phase.rb
lib/html5/inputstream.rb
lib/html5/liberalxmlparser.rb
lib/html5/sanitizer.rb
lib/html5/serializer.rb
lib/html5/serializer/htmlserializer.rb
lib/html5/serializer/xhtmlserializer.rb
lib/html5/sniffer.rb
lib/html5/tokenizer.rb
lib/html5/treebuilders.rb
lib/html5/treebuilders/base.rb
lib/html5/treebuilders/hpricot.rb
lib/html5/treebuilders/rexml.rb
lib/html5/treebuilders/simpletree.rb
lib/html5/treewalkers.rb
lib/html5/treewalkers/base.rb
lib/html5/treewalkers/hpricot.rb
lib/html5/treewalkers/rexml.rb
lib/html5/treewalkers/simpletree.rb
lib/html5/version.rb
testdata/encoding/chardet/test_big5.txt
testdata/encoding/test-yahoo-jp.dat
testdata/encoding/tests1.dat
testdata/encoding/tests2.dat
testdata/sanitizer/tests1.dat
testdata/serializer/core.test
testdata/serializer/injectmeta.test
testdata/serializer/optionaltags.test
testdata/serializer/options.test
testdata/serializer/whitespace.test
testdata/sites/google-results.htm
testdata/sites/python-ref-import.htm
testdata/sites/web-apps-old.htm
testdata/sites/web-apps.htm
testdata/sniffer/htmlOrFeed.json
testdata/tokenizer/contentModelFlags.test
testdata/tokenizer/entities.test
testdata/tokenizer/escapeFlag.test
testdata/tokenizer/test1.test
testdata/tokenizer/test2.test
testdata/tokenizer/test3.test
testdata/tokenizer/test4.test
testdata/tree-construction/tests1.dat
testdata/tree-construction/tests2.dat
testdata/tree-construction/tests3.dat
testdata/tree-construction/tests4.dat
testdata/tree-construction/tests5.dat
testdata/tree-construction/tests6.dat
testdata/validator/attributes.test
testdata/validator/base-href-attribute.test
testdata/validator/base-target-attribute.test
testdata/validator/blockquote-cite-attribute.test
testdata/validator/classattribute.test
testdata/validator/contenteditableattribute.test
testdata/validator/contextmenuattribute.test
testdata/validator/dirattribute.test
testdata/validator/draggableattribute.test
testdata/validator/html-xmlns-attribute.test
testdata/validator/idattribute.test
testdata/validator/inputattributes.test
testdata/validator/irrelevantattribute.test
testdata/validator/langattribute.test
testdata/validator/li-value-attribute.test
testdata/validator/link-href-attribute.test
testdata/validator/link-hreflang-attribute.test
testdata/validator/link-rel-attribute.test
testdata/validator/ol-start-attribute.test
testdata/validator/starttags.test
testdata/validator/style-scoped-attribute.test
testdata/validator/tabindexattribute.test
test/preamble.rb
test/test_encoding.rb
test/test_lxp.rb
test/test_parser.rb
test/test_sanitizer.rb
test/test_serializer.rb
test/test_sniffer.rb
test/test_stream.rb
test/test_tokenizer.rb
test/test_treewalkers.rb
test/test_validator.rb
test/tokenizer_test_parser.rb

View file

@ -1,45 +0,0 @@
html5
by Ryan King, et al
http://code.google.com/p/html5lib
== DESCRIPTION:
A ruby implementation of the parsing algorithm in HTML5.
== FEATURES/PROBLEMS:
== SYNOPSIS:
TODO
== REQUIREMENTS:
* chardet, only tested with 0.9.0
== INSTALL:
* sudo gem install html5
== LICENSE:
Copyright (c) 2006-2007 The Authors
Contributers:
James Graham - jg307@cam.ac.uk
Anne van Kesteren - annevankesteren@gmail.com
Lachlan Hunt - lachlan.hunt@lachy.id.au
Matt McDonald - kanashii@kanashii.ca
Sam Ruby - rubys@intertwingly.net
Ian Hickson (Google) - ian@hixie.ch
Thomas Broyer - t.broyer@ltgt.net
Jacques Distler - distler@golem.ph.utexas.edu
Ryan King - ryan@theryanking.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,33 +0,0 @@
require 'rake'
require 'hoe'
require 'lib/html5/version'
Hoe.new("html5", HTML5::VERSION) do |p|
p.name = "html5"
p.description = p.paragraphs_of('README', 2..5).join("\n\n")
p.summary = "HTML5 parser/tokenizer."
p.author = ['Ryan King'] # TODO: add more names
p.email = 'ryan@theryanking.com'
p.url = 'http://code.google.com/p/html5lib'
p.need_zip = true
p.extra_deps << ['chardet', '>= 0.9.0']
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
end
require 'rcov/rcovtask'
namespace :test do
namespace :coverage do
desc "Delete aggregate coverage data."
task(:clean) { rm_f "coverage.data" }
end
desc 'Aggregate code coverage for unit, functional and integration tests'
Rcov::RcovTask.new(:coverage => "test:coverage:clean") do |t|
t.libs << "test"
t.test_files = FileList["test/test_*.rb"]
t.output_dir = "test/coverage/"
t.verbose = true
end
end

View file

@ -1,5 +0,0 @@
#!/usr/bin/env ruby
require 'html5/cli'
HTML5::CLI.run

View file

@ -1,13 +0,0 @@
require 'html5/html5parser'
require 'html5/version'
module HTML5
def self.parse(stream, options={})
HTMLParser.parse(stream, options)
end
def self.parse_fragment(stream, options={})
HTMLParser.parse_fragment(stream, options)
end
end

View file

@ -1,248 +0,0 @@
$:.unshift File.dirname(__FILE__), 'lib'
require 'html5'
require 'ostruct'
require 'optparse'
module HTML5::CLI
def self.parse_opts argv
options = OpenStruct.new
options.profile = false
options.time = false
options.output = :html
options.treebuilder = 'simpletree'
options.error = false
options.encoding = false
options.parsemethod = :parse
options.serializer = {
:encoding => 'utf-8',
:omit_optional_tags => false,
:inject_meta_charset => false
}
opts = OptionParser.new do |opts|
opts.separator ""
opts.separator "Parse Options:"
opts.on("-b", "--treebuilder NAME") do |treebuilder|
options.treebuilder = treebuilder
end
opts.on("-f", "--fragment CONTAINER", "Parse as a fragment") do |container|
options.parsemethod = :parse_fragment
options.container = container if container
end
opts.separator ""
opts.separator "Filter Options:"
opts.on("--[no-]inject-meta-charset", "inject <meta charset>") do |inject|
options.serializer[:inject_meta_charset] = inject
end
opts.on("--[no-]strip-whitespace", "strip unnecessary whitespace") do |strip|
options.serializer[:strip_whitespace] = strip
end
opts.on("--[no-]sanitize", "escape unsafe tags") do |sanitize|
options.serializer[:sanitize] = sanitize
end
opts.separator ""
opts.separator "Output Options:"
opts.on("--tree", "output as debug tree") do |tree|
options.output = :tree
end
opts.on("-x", "--xml", "output as xml") do |xml|
options.output = :xml
options.treebuilder = "rexml"
end
opts.on("--[no-]html", "Output as html") do |html|
options.output = (html ? :html : nil)
end
opts.on("--hilite", "Output as formatted highlighted code.") do |hilite|
options.output = :hilite
end
opts.on("-e", "--error", "Print a list of parse errors") do |error|
options.error = error
end
opts.separator ""
opts.separator "Serialization Options:"
opts.on("--[no-]omit-optional-tags", "Omit optional tags") do |omit|
options.serializer[:omit_optional_tags] = omit
end
opts.on("--[no-]quote-attr-values", "Quote attribute values") do |quote|
options.serializer[:quote_attr_values] = quote
end
opts.on("--[no-]use-best-quote-char", "Use best quote character") do |best|
options.serializer[:use_best_quote_char] = best
end
opts.on("--quote-char C", "Use specified quote character") do |c|
options.serializer[:quote_char] = c
end
opts.on("--[no-]minimize-boolean-attributes", "Minimize boolean attributes") do |min|
options.serializer[:minimize_boolean_attributes] = min
end
opts.on("--[no-]use-trailing-solidus", "Use trailing solidus") do |slash|
options.serializer[:use_trailing_solidus] = slash
end
opts.on("--[no-]escape-lt-in-attrs", "Escape less than signs in attribute values") do |lt|
options.serializer[:escape_lt_in_attrs] = lt
end
opts.on("--[no-]escape-rcdata", "Escape rcdata element values") do |rcdata|
options.serializer[:escape_rcdata] = rcdata
end
opts.separator ""
opts.separator "Other Options:"
opts.on("-p", "--[no-]profile", "Profile the run") do |profile|
options.profile = profile
end
opts.on("-t", "--[no-]time", "Time the run") do |time|
options.time = time
end
opts.on("-c", "--[no-]encoding", "Print character encoding used") do |encoding|
options.encoding = encoding
end
opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit
end
end
opts.parse!(argv)
options
end
def self.open_input f
if f
begin
if f[0..6] == 'http://'
require 'open-uri'
f = URI.parse(f).open
encoding = f.charset
elsif f == '-'
f = $stdin
else
f = open(f)
end
rescue
end
else
$stderr.write("No filename provided. Use -h for help\n")
exit(1)
end
f
end
def self.parse(opts, args)
encoding = nil
f = open_input args.last
require 'html5/treebuilders'
treebuilder = HTML5::TreeBuilders[opts.treebuilder]
if opts.output == :xml
require 'html5/liberalxmlparser'
p = HTML5::XMLParser.new(:tree=>treebuilder)
else
require 'html5/html5parser'
p = HTML5::HTMLParser.new(:tree=>treebuilder)
end
if opts.parsemethod == :parse
args = [f, encoding]
else
args = [f, (opts.container || 'div'), encoding]
end
if opts.profile
require 'profiler'
Profiler__::start_profile
p.send(opts.parsemethod, *args)
Profiler__::stop_profile
Profiler__::print_profile($stderr)
elsif opts.time
require 'time' # TODO: switch to benchmark
t0 = Time.new
document = p.send(opts.parsemethod, *args)
t1 = Time.new
print_output(p, document, opts)
t2 = Time.new
puts "\n\nRun took: #{t1-t0}s (plus #{t2-t1}s to print the output)"
else
document = p.send(opts.parsemethod, *args)
print_output(p, document, opts)
end
end
def self.print_output(parser, document, opts)
puts "Encoding: #{parser.tokenizer.stream.char_encoding}" if opts.encoding
case opts.output
when :xml
print document
when :html
require 'html5/treewalkers'
tokens = HTML5::TreeWalkers[opts.treebuilder].new(document)
require 'html5/serializer'
puts HTML5::HTMLSerializer.serialize(tokens, opts.serializer)
when :hilite
print document.hilite
when :tree
document = [document] unless document.respond_to?(:each)
document.each {|fragment| puts parser.tree.testSerializer(fragment)}
end
if opts.error
errList=[]
for pos, errorcode, datavars in parser.errors
formatstring = HTML5::E[errorcode] || 'Unknown error "%(errorcode)"'
message = PythonicTemplate.new(formatstring).to_s(datavars)
errList << "Line #{pos[0]} Col #{pos[1]} " + message
end
$stdout.write("\nParse errors:\n" + errList.join("\n")+"\n")
end
end
class PythonicTemplate
# convert Python format string into a Ruby string, ready to eval
def initialize format
@format = format
@format.gsub!('"', '\\"')
@format.gsub!(/%\((\w+)\)/, '#{@_\1}')
@format = '"' + @format + '"'
end
# evaluate string
def to_s(vars=nil)
vars.each {|var,value| eval "@_#{var}=#{value.dump}"} if vars
eval @format
end
end
def self.run
options = parse_opts ARGV
parse options, ARGV
end
end

File diff suppressed because it is too large Load diff

View file

@ -1,10 +0,0 @@
require 'delegate'
require 'enumerator'
module HTML5
module Filters
class Base < SimpleDelegator
include Enumerable
end
end
end

View file

@ -1,82 +0,0 @@
require 'html5/filters/base'
module HTML5
module Filters
class InjectMetaCharset < Base
def initialize(source, encoding)
super(source)
@encoding = encoding
end
def each
state = :pre_head
meta_found = @encoding.nil?
pending = []
__getobj__.each do |token|
case token[:type]
when :StartTag
state = :in_head if token[:name].downcase == "head"
when :EmptyTag
if token[:name].downcase == "meta"
# replace charset with actual encoding
token[:data].each_with_index do |(name, value), index|
if name == 'charset'
token[:data][index][1] = @encoding
meta_found = true
end
end
# replace charset with actual encoding
has_http_equiv_content_type = false
content_index = -1
token[:data].each_with_index do |(name, value), i|
if name.downcase == 'charset'
token[:data][i] = ['charset', @encoding]
meta_found = true
break
elsif name == 'http-equiv' and value.downcase == 'content-type'
has_http_equiv_content_type = true
elsif name == 'content'
content_index = i
end
end
if !meta_found
if has_http_equiv_content_type && content_index >= 0
token[:data][content_index][1] = 'text/html; charset=%s' % @encoding
meta_found = true
end
end
elsif token[:name].downcase == "head" && !meta_found
# insert meta into empty head
yield :type => :StartTag, :name => "head", :data => token[:data]
yield :type => :EmptyTag, :name => "meta", :data => [["charset", @encoding]]
yield :type => :EndTag, :name => "head"
meta_found = true
next
end
when :EndTag
if token[:name].downcase == "head" && pending.any?
# insert meta into head (if necessary) and flush pending queue
yield pending.shift
yield :type => :EmptyTag, :name => "meta", :data => [["charset", @encoding]] if !meta_found
yield pending.shift while pending.any?
meta_found = true
state = :post_head
end
end
if state == :in_head
pending << token
else
yield token
end
end
end
end
end
end

View file

@ -1,752 +0,0 @@
# borrowed from feedvalidator, original copyright license is
#
# Copyright (c) 2002-2006, Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
ISO_LANG = {
'aa' => 'Afar',
'ab' => 'Abkhazian',
'ae' => 'Avestan',
'af' => 'Afrikaans',
'ak' => 'Akan',
'am' => 'Amharic',
'an' => 'Aragonese',
'ar' => 'Arabic',
'as' => 'Assamese',
'av' => 'Avaric',
'ay' => 'Aymara',
'az' => 'Azerbaijani',
'ba' => 'Bashkir',
'be' => 'Byelorussian',
'bg' => 'Bulgarian',
'bh' => 'Bihari',
'bi' => 'Bislama',
'bm' => 'Bambara',
'bn' => 'Bengali;Bangla',
'bo' => 'Tibetan',
'br' => 'Breton',
'bs' => 'Bosnian',
'ca' => 'Catalan',
'ce' => 'Chechen',
'ch' => 'Chamorro',
'co' => 'Corsican',
'cr' => 'Cree',
'cs' => 'Czech',
'cu' => 'Church Slavic',
'cv' => 'Chuvash',
'cy' => 'Welsh',
'da' => 'Danish',
'de' => 'German',
'dv' => 'Divehi',
'dz' => 'Dzongkha',
'ee' => 'Ewe',
'el' => 'Greek',
'en' => 'English',
'eo' => 'Esperanto',
'es' => 'Spanish',
'et' => 'Estonian',
'eu' => 'Basque',
'fa' => 'Persian (Farsi)',
'ff' => 'Fulah',
'fi' => 'Finnish',
'fj' => 'Fiji',
'fo' => 'Faroese',
'fr' => 'French',
'fy' => 'Frisian, Western',
'ga' => 'Irish',
'gd' => 'Scots Gaelic',
'gl' => 'Galician',
'gn' => 'Guarani',
'gu' => 'Gujarati',
'gv' => 'Manx',
'ha' => 'Hausa',
'he' => 'Hebrew',
'hi' => 'Hindi',
'ho' => 'Hiri Motu',
'hr' => 'Croatian',
'ht' => 'Haitian',
'hu' => 'Hungarian',
'hy' => 'Armenian',
'hz' => 'Herero',
'ia' => 'Interlingua',
'id' => 'Indonesian',
'ie' => 'Interlingue',
'ig' => 'Igbo',
'ii' => 'Sichuan Yi',
'ik' => 'Inupiak',
'io' => 'Ido',
'is' => 'Icelandic',
'it' => 'Italian',
'iu' => 'Inuktitut',
'ja' => 'Japanese',
'jv' => 'Javanese',
'ka' => 'Georgian',
'kg' => 'Kongo',
'ki' => 'Kikuyu; Gikuyu',
'kj' => 'Kuanyama; Kwanyama',
'kk' => 'Kazakh',
'kl' => 'Greenlandic',
'km' => 'Cambodian',
'kn' => 'Kannada',
'ko' => 'Korean',
'kr' => 'Kanuri',
'ks' => 'Kashmiri',
'ku' => 'Kurdish',
'kv' => 'Komi',
'kw' => 'Cornish',
'ky' => 'Kirghiz',
'la' => 'Latin',
'lb' => 'Letzeburgesch; Luxembourgish',
'lg' => 'Ganda',
'li' => 'Limburgan; Limburger, Limburgish',
'ln' => 'Lingala',
'lo' => 'Lao',
'lt' => 'Lithuanian',
'lu' => 'Luba-Katanga',
'lv' => 'Latvian',
'mg' => 'Malagasy',
'mh' => 'Marshallese',
'mi' => 'Maori',
'mk' => 'Macedonian',
'ml' => 'Malayalam',
'mn' => 'Mongolian',
'mo' => 'Moldavian',
'mr' => 'Marathi',
'ms' => 'Malay',
'mt' => 'Maltese',
'my' => 'Burmese',
'na' => 'Nauru',
'nb' => 'Norwegian Bokmal',
'nd' => 'Ndebele, North',
'ne' => 'Nepali',
'ng' => 'Ndonga',
'nl' => 'Dutch',
'nn' => 'Norwegian Nynorsk',
'no' => 'Norwegian',
'nr' => 'Ndebele, South',
'nv' => 'Navaho; Navajo',
'ny' => 'Chewa; Chichewa; Nyanha',
'oc' => 'Occitan',
'oj' => 'Ojibwa',
'om' => 'Afan (Oromo)',
'or' => 'Oriya',
'os' => 'Ossetian; Ossetic',
'pa' => 'Punjabi',
'pi' => 'Pali',
'pl' => 'Polish',
'ps' => 'Pushto',
'pt' => 'Portuguese',
'qu' => 'Quechua',
'rm' => 'Rhaeto-Romance',
'rn' => 'Kurundi',
'ro' => 'Romanian',
'ru' => 'Russian',
'rw' => 'Kinyarwanda',
'sa' => 'Sanskrit',
'sc' => 'Sardinian',
'sd' => 'Sindhi',
'se' => 'Northern Sami',
'sg' => 'Sangho',
'sh' => 'Serbo-Croatian',
'si' => 'Singhalese',
'sk' => 'Slovak',
'sl' => 'Slovenian',
'sm' => 'Samoan',
'sn' => 'Shona',
'so' => 'Somali',
'sq' => 'Albanian',
'sr' => 'Serbian',
'ss' => 'Swati',
'st' => 'Sotho, Southern',
'su' => 'Sundanese',
'sv' => 'Swedish',
'sw' => 'Swahili',
'ta' => 'Tamil',
'te' => 'Telugu',
'tg' => 'Tajik',
'th' => 'Thai',
'ti' => 'Tigrinya',
'tk' => 'Turkmen',
'tl' => 'Tagalog',
'tn' => 'Tswana',
'to' => 'Tonga',
'tr' => 'Turkish',
'ts' => 'Tsonga',
'tt' => 'Tatar',
'tw' => 'Twi',
'ty' => 'Tahitian',
'ug' => 'Uigur',
'uk' => 'Ukrainian',
'ur' => 'Urdu',
'uz' => 'Uzbek',
've' => 'Venda',
'vi' => 'Vietnamese',
'vo' => 'Volapuk',
'wa' => 'Walloon',
'wo' => 'Wolof',
'xh' => 'Xhosa',
'yi' => 'Yiddish',
'yo' => 'Yoruba',
'za' => 'Zhuang',
'zh' => 'Chinese',
'zu' => 'Zulu',
'x' => 'a user-defined language',
'xx' => 'a user-defined language',
'abk' => 'Abkhazian',
'ace' => 'Achinese',
'ach' => 'Acoli',
'ada' => 'Adangme',
'ady' => 'Adygei',
'ady' => 'Adyghe',
'aar' => 'Afar',
'afh' => 'Afrihili',
'afr' => 'Afrikaans',
'afa' => 'Afro-Asiatic (Other)',
'ain' => 'Ainu',
'aka' => 'Akan',
'akk' => 'Akkadian',
'alb' => 'Albanian',
'sqi' => 'Albanian',
'gws' => 'Alemanic',
'ale' => 'Aleut',
'alg' => 'Algonquian languages',
'tut' => 'Altaic (Other)',
'amh' => 'Amharic',
'anp' => 'Angika',
'apa' => 'Apache languages',
'ara' => 'Arabic',
'arg' => 'Aragonese',
'arc' => 'Aramaic',
'arp' => 'Arapaho',
'arn' => 'Araucanian',
'arw' => 'Arawak',
'arm' => 'Armenian',
'hye' => 'Armenian',
'rup' => 'Aromanian',
'art' => 'Artificial (Other)',
'asm' => 'Assamese',
'ast' => 'Asturian',
'ath' => 'Athapascan languages',
'aus' => 'Australian languages',
'map' => 'Austronesian (Other)',
'ava' => 'Avaric',
'ave' => 'Avestan',
'awa' => 'Awadhi',
'aym' => 'Aymara',
'aze' => 'Azerbaijani',
'ast' => 'Bable',
'ban' => 'Balinese',
'bat' => 'Baltic (Other)',
'bal' => 'Baluchi',
'bam' => 'Bambara',
'bai' => 'Bamileke languages',
'bad' => 'Banda',
'bnt' => 'Bantu (Other)',
'bas' => 'Basa',
'bak' => 'Bashkir',
'baq' => 'Basque',
'eus' => 'Basque',
'btk' => 'Batak (Indonesia)',
'bej' => 'Beja',
'bel' => 'Belarusian',
'bem' => 'Bemba',
'ben' => 'Bengali',
'ber' => 'Berber (Other)',
'bho' => 'Bhojpuri',
'bih' => 'Bihari',
'bik' => 'Bikol',
'byn' => 'Bilin',
'bin' => 'Bini',
'bis' => 'Bislama',
'byn' => 'Blin',
'nob' => 'Bokmal, Norwegian',
'bos' => 'Bosnian',
'bra' => 'Braj',
'bre' => 'Breton',
'bug' => 'Buginese',
'bul' => 'Bulgarian',
'bua' => 'Buriat',
'bur' => 'Burmese',
'mya' => 'Burmese',
'cad' => 'Caddo',
'car' => 'Carib',
'spa' => 'Castilian',
'cat' => 'Catalan',
'cau' => 'Caucasian (Other)',
'ceb' => 'Cebuano',
'cel' => 'Celtic (Other)',
'cai' => 'Central American Indian (Other)',
'chg' => 'Chagatai',
'cmc' => 'Chamic languages',
'cha' => 'Chamorro',
'che' => 'Chechen',
'chr' => 'Cherokee',
'nya' => 'Chewa',
'chy' => 'Cheyenne',
'chb' => 'Chibcha',
'nya' => 'Chichewa',
'chi' => 'Chinese',
'zho' => 'Chinese',
'chn' => 'Chinook jargon',
'chp' => 'Chipewyan',
'cho' => 'Choctaw',
'zha' => 'Chuang',
'chu' => 'Church Slavic; Church Slavonic; Old Church Slavonic; Old Church Slavic; Old Bulgarian',
'chk' => 'Chuukese',
'chv' => 'Chuvash',
'nwc' => 'Classical Nepal Bhasa; Classical Newari; Old Newari',
'cop' => 'Coptic',
'cor' => 'Cornish',
'cos' => 'Corsican',
'cre' => 'Cree',
'mus' => 'Creek',
'crp' => 'Creoles and pidgins(Other)',
'cpe' => 'Creoles and pidgins, English-based (Other)',
'cpf' => 'Creoles and pidgins, French-based (Other)',
'cpp' => 'Creoles and pidgins, Portuguese-based (Other)',
'crh' => 'Crimean Tatar; Crimean Turkish',
'scr' => 'Croatian',
'hrv' => 'Croatian',
'cus' => 'Cushitic (Other)',
'cze' => 'Czech',
'ces' => 'Czech',
'dak' => 'Dakota',
'dan' => 'Danish',
'dar' => 'Dargwa',
'day' => 'Dayak',
'del' => 'Delaware',
'din' => 'Dinka',
'div' => 'Divehi',
'doi' => 'Dogri',
'dgr' => 'Dogrib',
'dra' => 'Dravidian (Other)',
'dua' => 'Duala',
'dut' => 'Dutch',
'nld' => 'Dutch',
'dum' => 'Dutch, Middle (ca. 1050-1350)',
'dyu' => 'Dyula',
'dzo' => 'Dzongkha',
'efi' => 'Efik',
'egy' => 'Egyptian (Ancient)',
'eka' => 'Ekajuk',
'elx' => 'Elamite',
'eng' => 'English',
'enm' => 'English, Middle (1100-1500)',
'ang' => 'English, Old (ca.450-1100)',
'myv' => 'Erzya',
'epo' => 'Esperanto',
'est' => 'Estonian',
'ewe' => 'Ewe',
'ewo' => 'Ewondo',
'fan' => 'Fang',
'fat' => 'Fanti',
'fao' => 'Faroese',
'fij' => 'Fijian',
'fil' => 'Filipino; Pilipino',
'fin' => 'Finnish',
'fiu' => 'Finno-Ugrian (Other)',
'fon' => 'Fon',
'fre' => 'French',
'fra' => 'French',
'frm' => 'French, Middle (ca.1400-1600)',
'fro' => 'French, Old (842-ca.1400)',
'frs' => 'Frisian, Eastern',
'fry' => 'Frisian, Western',
'fur' => 'Friulian',
'ful' => 'Fulah',
'gaa' => 'Ga',
'gla' => 'Gaelic',
'glg' => 'Gallegan',
'lug' => 'Ganda',
'gay' => 'Gayo',
'gba' => 'Gbaya',
'gez' => 'Geez',
'geo' => 'Georgian',
'kat' => 'Georgian',
'ger' => 'German',
'deu' => 'German',
'nds' => 'German, Low',
'gmh' => 'German, Middle High (ca.1050-1500)',
'goh' => 'German, Old High (ca.750-1050)',
'gem' => 'Germanic (Other)',
'kik' => 'Gikuyu',
'gil' => 'Gilbertese',
'gon' => 'Gondi',
'gor' => 'Gorontalo',
'got' => 'Gothic',
'grb' => 'Grebo',
'grc' => 'Greek, Ancient (to 1453)',
'gre' => 'Greek, Modern (1453-)',
'ell' => 'Greek, Modern (1453-)',
'kal' => 'Greenlandic; Kalaallisut',
'grn' => 'Guarani',
'guj' => 'Gujarati',
'gwi' => 'Gwich\'in',
'hai' => 'Haida',
'hat' => 'Haitian',
'hau' => 'Hausa',
'haw' => 'Hawaiian',
'heb' => 'Hebrew',
'her' => 'Herero',
'hil' => 'Hiligaynon',
'him' => 'Himachali',
'hin' => 'Hindi',
'hmo' => 'Hiri Motu',
'hit' => 'Hittite',
'hmn' => 'Hmong',
'hun' => 'Hungarian',
'hup' => 'Hupa',
'iba' => 'Iban',
'ice' => 'Icelandic',
'isl' => 'Icelandic',
'ido' => 'Ido',
'ibo' => 'Igbo',
'ijo' => 'Ijo',
'ilo' => 'Iloko',
'smn' => 'Inari Sami',
'inc' => 'Indic (Other)',
'ine' => 'Indo-European (Other)',
'ind' => 'Indonesian',
'inh' => 'Ingush',
'ina' => 'Interlingua (International Auxiliary Language Association)',
'ile' => 'Interlingue',
'iku' => 'Inuktitut',
'ipk' => 'Inupiaq',
'ira' => 'Iranian (Other)',
'gle' => 'Irish',
'mga' => 'Irish, Middle (900-1200)',
'sga' => 'Irish, Old (to 900)',
'iro' => 'Iroquoian languages',
'ita' => 'Italian',
'jpn' => 'Japanese',
'jav' => 'Javanese',
'jrb' => 'Judeo-Arabic',
'jpr' => 'Judeo-Persian',
'kbd' => 'Kabardian',
'kab' => 'Kabyle',
'kac' => 'Kachin',
'kal' => 'Kalaallisut',
'xal' => 'Kalmyk',
'kam' => 'Kamba',
'kan' => 'Kannada',
'kau' => 'Kanuri',
'krc' => 'Karachay-Balkar',
'kaa' => 'Kara-Kalpak',
'krl' => 'Karelian',
'kar' => 'Karen',
'kas' => 'Kashmiri',
'csb' => 'Kashubian',
'kaw' => 'Kawi',
'kaz' => 'Kazakh',
'kha' => 'Khasi',
'khm' => 'Khmer',
'khi' => 'Khoisan (Other)',
'kho' => 'Khotanese',
'kik' => 'Kikuyu',
'kmb' => 'Kimbundu',
'kin' => 'Kinyarwanda',
'kir' => 'Kirghiz',
'tlh' => 'Klingon; tlhIngan-Hol',
'kom' => 'Komi',
'kon' => 'Kongo',
'kok' => 'Konkani',
'kor' => 'Korean',
'kos' => 'Kosraean',
'kpe' => 'Kpelle',
'kro' => 'Kru',
'kua' => 'Kuanyama',
'kum' => 'Kumyk',
'kur' => 'Kurdish',
'kru' => 'Kurukh',
'kut' => 'Kutenai',
'kua' => 'Kwanyama',
'lad' => 'Ladino',
'lah' => 'Lahnda',
'lam' => 'Lamba',
'lao' => 'Lao',
'lat' => 'Latin',
'lav' => 'Latvian',
'ltz' => 'Letzeburgesch',
'lez' => 'Lezghian',
'lim' => 'Limburgan',
'lin' => 'Lingala',
'lit' => 'Lithuanian',
'jbo' => 'Lojban',
'nds' => 'Low German',
'dsb' => 'Lower Sorbian',
'loz' => 'Lozi',
'lub' => 'Luba-Katanga',
'lua' => 'Luba-Lulua',
'lui' => 'Luiseno',
'smj' => 'Lule Sami',
'lun' => 'Lunda',
'luo' => 'Luo (Kenya and Tanzania)',
'lus' => 'Lushai',
'ltz' => 'Luxembourgish',
'mac' => 'Macedonian',
'mkd' => 'Macedonian',
'mad' => 'Madurese',
'mag' => 'Magahi',
'mai' => 'Maithili',
'mak' => 'Makasar',
'mlg' => 'Malagasy',
'may' => 'Malay',
'msa' => 'Malay',
'mal' => 'Malayalam',
'mlt' => 'Maltese',
'mnc' => 'Manchu',
'mdr' => 'Mandar',
'man' => 'Mandingo',
'mni' => 'Manipuri',
'mno' => 'Manobo languages',
'glv' => 'Manx',
'mao' => 'Maori',
'mri' => 'Maori',
'mar' => 'Marathi',
'chm' => 'Mari',
'mah' => 'Marshallese',
'mwr' => 'Marwari',
'mas' => 'Masai',
'myn' => 'Mayan languages',
'men' => 'Mende',
'mic' => 'Micmac',
'min' => 'Minangkabau',
'mwl' => 'Mirandese',
'mis' => 'Miscellaneous languages',
'moh' => 'Mohawk',
'mdf' => 'Moksha',
'mol' => 'Moldavian',
'mkh' => 'Mon-Khmer (Other)',
'lol' => 'Mongo',
'mon' => 'Mongolian',
'mos' => 'Mossi',
'mul' => 'Multiple languages',
'mun' => 'Munda languages',
'nah' => 'Nahuatl',
'nau' => 'Nauru',
'nav' => 'Navaho; Navajo',
'nde' => 'Ndebele, North',
'nbl' => 'Ndebele, South',
'ndo' => 'Ndonga',
'nap' => 'Neapolitan',
'nep' => 'Nepali',
'new' => 'Newari',
'nia' => 'Nias',
'nic' => 'Niger-Kordofanian (Other)',
'ssa' => 'Nilo-Saharan (Other)',
'niu' => 'Niuean',
'nog' => 'Nogai',
'non' => 'Norse, Old',
'nai' => 'North American Indian (Other)',
'frr' => 'Northern Frisian',
'sme' => 'Northern Sami',
'nso' => 'Northern Sotho; Pedi; Sepedi',
'nde' => 'North Ndebele',
'nor' => 'Norwegian',
'nob' => 'Norwegian Bokmal',
'nno' => 'Norwegian Nynorsk',
'nub' => 'Nubian languages',
'nym' => 'Nyamwezi',
'nya' => 'Nyanja',
'nyn' => 'Nyankole',
'nno' => 'Nynorsk, Norwegian',
'nyo' => 'Nyoro',
'nzi' => 'Nzima',
'oci' => 'Occitan (post 1500)',
'oji' => 'Ojibwa',
'ori' => 'Oriya',
'orm' => 'Oromo',
'osa' => 'Osage',
'oss' => 'Ossetian; Ossetic',
'oto' => 'Otomian languages',
'pal' => 'Pahlavi',
'pau' => 'Palauan',
'pli' => 'Pali',
'pam' => 'Pampanga',
'pag' => 'Pangasinan',
'pan' => 'Panjabi',
'pap' => 'Papiamento',
'paa' => 'Papuan (Other)',
'per' => 'Persian',
'fas' => 'Persian',
'peo' => 'Persian, Old (ca.600-400)',
'phi' => 'Philippine (Other)',
'phn' => 'Phoenician',
'pon' => 'Pohnpeian',
'pol' => 'Polish',
'por' => 'Portuguese',
'pra' => 'Prakrit languages',
'oci' => 'Provencal',
'pro' => 'Provencal, Old (to 1500)',
'pan' => 'Punjabi',
'pus' => 'Pushto',
'que' => 'Quechua',
'roh' => 'Raeto-Romance',
'raj' => 'Rajasthani',
'rap' => 'Rapanui',
'rar' => 'Rarotongan',
'qaa' => 'Reserved for local use',
'qtz' => 'Reserved for local use',
'roa' => 'Romance (Other)',
'rum' => 'Romanian',
'ron' => 'Romanian',
'rom' => 'Romany',
'run' => 'Rundi',
'rus' => 'Russian',
'sal' => 'Salishan languages',
'sam' => 'Samaritan Aramaic',
'smi' => 'Sami languages (Other)',
'smo' => 'Samoan',
'sad' => 'Sandawe',
'sag' => 'Sango',
'san' => 'Sanskrit',
'sat' => 'Santali',
'srd' => 'Sardinian',
'sas' => 'Sasak',
'nds' => 'Saxon, Low',
'sco' => 'Scots',
'gla' => 'Scottish Gaelic',
'sel' => 'Selkup',
'sem' => 'Semitic (Other)',
'nso' => 'Sepedi; Northern Sotho; Pedi',
'scc' => 'Serbian',
'srp' => 'Serbian',
'srr' => 'Serer',
'shn' => 'Shan',
'sna' => 'Shona',
'iii' => 'Sichuan Yi',
'scn' => 'Sicilian',
'sid' => 'Sidamo',
'sgn' => 'Sign languages',
'bla' => 'Siksika',
'snd' => 'Sindhi',
'sin' => 'Sinhalese',
'sit' => 'Sino-Tibetan (Other)',
'sio' => 'Siouan languages',
'sms' => 'Skolt Sami',
'den' => 'Slave (Athapascan)',
'sla' => 'Slavic (Other)',
'slo' => 'Slovak',
'slk' => 'Slovak',
'slv' => 'Slovenian',
'sog' => 'Sogdian',
'som' => 'Somali',
'son' => 'Songhai',
'snk' => 'Soninke',
'wen' => 'Sorbian languages',
'nso' => 'Sotho, Northern',
'sot' => 'Sotho, Southern',
'sai' => 'South American Indian (Other)',
'alt' => 'Southern Altai',
'sma' => 'Southern Sami',
'nbl' => 'South Ndebele',
'spa' => 'Spanish',
'srn' => 'Sranan Tongo',
'suk' => 'Sukuma',
'sux' => 'Sumerian',
'sun' => 'Sundanese',
'sus' => 'Susu',
'swa' => 'Swahili',
'ssw' => 'Swati',
'swe' => 'Swedish',
'gsw' => 'Swiss German; Alemanic',
'syr' => 'Syriac',
'tgl' => 'Tagalog',
'tah' => 'Tahitian',
'tai' => 'Tai (Other)',
'tgk' => 'Tajik',
'tmh' => 'Tamashek',
'tam' => 'Tamil',
'tat' => 'Tatar',
'tel' => 'Telugu',
'ter' => 'Tereno',
'tet' => 'Tetum',
'tha' => 'Thai',
'tib' => 'Tibetan',
'bod' => 'Tibetan',
'tig' => 'Tigre',
'tir' => 'Tigrinya',
'tem' => 'Timne',
'tiv' => 'Tiv',
'tlh' => 'tlhIngan-Hol; Klingon',
'tli' => 'Tlingit',
'tpi' => 'Tok Pisin',
'tkl' => 'Tokelau',
'tog' => 'Tonga (Nyasa)',
'ton' => 'Tonga (Tonga Islands)',
'tsi' => 'Tsimshian',
'tso' => 'Tsonga',
'tsn' => 'Tswana',
'tum' => 'Tumbuka',
'tup' => 'Tupi languages',
'tur' => 'Turkish',
'ota' => 'Turkish, Ottoman (1500-1928)',
'tuk' => 'Turkmen',
'tvl' => 'Tuvalu',
'tyv' => 'Tuvinian',
'twi' => 'Twi',
'udm' => 'Udmurt',
'uga' => 'Ugaritic',
'uig' => 'Uighur',
'ukr' => 'Ukrainian',
'umb' => 'Umbundu',
'und' => 'Undetermined',
'hsb' => 'Upper Sorbian',
'urd' => 'Urdu',
'uzb' => 'Uzbek',
'vai' => 'Vai',
'cat' => 'Valencian',
'ven' => 'Venda',
'vie' => 'Vietnamese',
'vol' => 'Volapuk',
'vot' => 'Votic',
'wak' => 'Wakashan languages',
'wal' => 'Walamo',
'wln' => 'Walloon',
'war' => 'Waray',
'was' => 'Washo',
'wel' => 'Welsh',
'cym' => 'Welsh',
'fry' => 'Wester Frisian',
'wol' => 'Wolof',
'xho' => 'Xhosa',
'sah' => 'Yakut',
'yao' => 'Yao',
'yap' => 'Yapese',
'yid' => 'Yiddish',
'yor' => 'Yoruba',
'ypk' => 'Yupik languages',
'znd' => 'Zande',
'zap' => 'Zapotec',
'zen' => 'Zenaga',
'zha' => 'Zhuang',
'zul' => 'Zulu',
'zun' => 'Zuni'
}
def is_valid_lang_code(value)
if value.include? '-'
lang, sublang = value.split('-', 2)
else
lang = value
end
!!ISO_LANG[lang.downcase]
end

View file

@ -1,198 +0,0 @@
require 'html5/constants'
require 'html5/filters/base'
module HTML5
module Filters
class OptionalTagFilter < Base
def slider
previous1 = previous2 = nil
__getobj__.each do |token|
yield previous2, previous1, token if previous1 != nil
previous2 = previous1
previous1 = token
end
yield previous2, previous1, nil
end
def each
slider do |previous, token, nexttok|
type = token[:type]
if type == :StartTag
yield token unless token[:data].empty? and is_optional_start(token[:name], previous, nexttok)
elsif type == :EndTag
yield token unless is_optional_end(token[:name], nexttok)
else
yield token
end
end
end
def is_optional_start(tagname, previous, nexttok)
type = nexttok ? nexttok[:type] : nil
if tagname == 'html'
# An html element's start tag may be omitted if the first thing
# inside the html element is not a space character or a comment.
return ![:Comment, :SpaceCharacters].include?(type)
elsif tagname == 'head'
# A head element's start tag may be omitted if the first thing
# inside the head element is an element.
return type == :StartTag
elsif tagname == 'body'
# A body element's start tag may be omitted if the first thing
# inside the body element is not a space character or a comment,
# except if the first thing inside the body element is a script
# or style element and the node immediately preceding the body
# element is a head element whose end tag has been omitted.
if [:Comment, :SpaceCharacters].include?(type)
return false
elsif type == :StartTag
# XXX: we do not look at the preceding event, so we never omit
# the body element's start tag if it's followed by a script or
# a style element.
return !%w[script style].include?(nexttok[:name])
else
return true
end
elsif tagname == 'colgroup'
# A colgroup element's start tag may be omitted if the first thing
# inside the colgroup element is a col element, and if the element
# is not immediately preceeded by another colgroup element whose
# end tag has been omitted.
if type == :StartTag
# XXX: we do not look at the preceding event, so instead we never
# omit the colgroup element's end tag when it is immediately
# followed by another colgroup element. See is_optional_end.
return nexttok[:name] == "col"
else
return false
end
elsif tagname == 'tbody'
# A tbody element's start tag may be omitted if the first thing
# inside the tbody element is a tr element, and if the element is
# not immediately preceeded by a tbody, thead, or tfoot element
# whose end tag has been omitted.
if type == :StartTag
# omit the thead and tfoot elements' end tag when they are
# immediately followed by a tbody element. See is_optional_end.
if previous and previous[:type] == :EndTag && %w(tbody thead tfoot).include?(previous[:name])
return false
end
return nexttok[:name] == 'tr'
else
return false
end
end
return false
end
def is_optional_end(tagname, nexttok)
type = nexttok ? nexttok[:type] : nil
if %w[html head body].include?(tagname)
# An html element's end tag may be omitted if the html element
# is not immediately followed by a space character or a comment.
return ![:Comment, :SpaceCharacters].include?(type)
elsif %w[li optgroup option tr].include?(tagname)
# A li element's end tag may be omitted if the li element is
# immediately followed by another li element or if there is
# no more content in the parent element.
# An optgroup element's end tag may be omitted if the optgroup
# element is immediately followed by another optgroup element,
# or if there is no more content in the parent element.
# An option element's end tag may be omitted if the option
# element is immediately followed by another option element,
# or if there is no more content in the parent element.
# A tr element's end tag may be omitted if the tr element is
# immediately followed by another tr element, or if there is
# no more content in the parent element.
if type == :StartTag
return nexttok[:name] == tagname
else
return type == :EndTag || type == nil
end
elsif %w(dt dd).include?(tagname)
# A dt element's end tag may be omitted if the dt element is
# immediately followed by another dt element or a dd element.
# A dd element's end tag may be omitted if the dd element is
# immediately followed by another dd element or a dt element,
# or if there is no more content in the parent element.
if type == :StartTag
return %w(dt dd).include?(nexttok[:name])
elsif tagname == 'dd'
return type == :EndTag || type == nil
else
return false
end
elsif tagname == 'p'
# A p element's end tag may be omitted if the p element is
# immediately followed by an address, blockquote, dl, fieldset,
# form, h1, h2, h3, h4, h5, h6, hr, menu, ol, p, pre, table,
# or ul element, or if there is no more content in the parent
# element.
if type == :StartTag
return %w(address blockquote dl fieldset form h1 h2 h3 h4 h5
h6 hr menu ol p pre table ul).include?(nexttok[:name])
else
return type == :EndTag || type == nil
end
elsif tagname == 'colgroup'
# A colgroup element's end tag may be omitted if the colgroup
# element is not immediately followed by a space character or
# a comment.
if [:Comment, :SpaceCharacters].include?(type)
return false
elsif type == :StartTag
# XXX: we also look for an immediately following colgroup
# element. See is_optional_start.
return nexttok[:name] != 'colgroup'
else
return true
end
elsif %w(thead tbody).include? tagname
# A thead element's end tag may be omitted if the thead element
# is immediately followed by a tbody or tfoot element.
# A tbody element's end tag may be omitted if the tbody element
# is immediately followed by a tbody or tfoot element, or if
# there is no more content in the parent element.
# A tfoot element's end tag may be omitted if the tfoot element
# is immediately followed by a tbody element, or if there is no
# more content in the parent element.
# XXX: we never omit the end tag when the following element is
# a tbody. See is_optional_start.
if type == :StartTag
return %w(tbody tfoot).include?(nexttok[:name])
elsif tagname == 'tbody'
return (type == :EndTag or type == nil)
else
return false
end
elsif tagname == 'tfoot'
# A tfoot element's end tag may be omitted if the tfoot element
# is immediately followed by a tbody element, or if there is no
# more content in the parent element.
# XXX: we never omit the end tag when the following element is
# a tbody. See is_optional_start.
if type == :StartTag
return nexttok[:name] == 'tbody'
else
return type == :EndTag || type == nil
end
elsif %w(td th).include? tagname
# A td element's end tag may be omitted if the td element is
# immediately followed by a td or th element, or if there is
# no more content in the parent element.
# A th element's end tag may be omitted if the th element is
# immediately followed by a td or th element, or if there is
# no more content in the parent element.
if type == :StartTag
return %w(td th).include?(nexttok[:name])
else
return type == :EndTag || type == nil
end
end
return false
end
end
end
end

Some files were not shown because too many files have changed in this diff Show more