Compare commits
1 commit
master
...
instiki-ma
Author | SHA1 | Date | |
---|---|---|---|
|
98848397d3 |
33474 changed files with 27553 additions and 547196 deletions
857
CHANGELOG
Normal file → Executable file
857
CHANGELOG
Normal file → Executable 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:
|
||||
- Upgraded to Rails 0.13.1
|
||||
- Fixed HTML export
|
||||
- Added layout=no option to the export_html action (it exports page
|
||||
contents processed by the markup engine, but without the default layout -
|
||||
so that they can be wrapped in some other layout)
|
||||
- <nowiki> tag can span several lines (before it was applied when both
|
||||
opening and closing tags were on the same line only)
|
||||
- Resolved the "endless redirection loop" condition and otherwise improved
|
||||
handling of errors in the rendering engines
|
||||
- Fixed rendering of Markdown hyperlinks such as
|
||||
[Text](http://something.com/foo)
|
||||
------------------------------------------------------------------------------
|
||||
Upgraded to Rails 0.13.1
|
||||
Fixed HTML export
|
||||
Added layout=no option to the export_html action (it exports page contents processed
|
||||
by the markup engine, but without the default layout - so that they can be wrapped in
|
||||
some other layout)
|
||||
<nowiki> tag can span several lines (before it was applied when both opening and closing
|
||||
tags were on the same line only)
|
||||
Resolved the "endless redirection loop" condition and otherwise improved handling of
|
||||
errors in the rendering engines
|
||||
Fixed rendering of Markdown hyperlinks such as [Text](http://something.com/foo)
|
||||
|
||||
* 0.10.1:
|
||||
- Upgraded Rails to 0.12.0
|
||||
- Upgraded rubyzip to version 0.5.8
|
||||
- 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)
|
||||
- Fixed HTML export (to enclose the output in <html> tags, include the
|
||||
stylesheet etc)
|
||||
- Corrected some compatibility issues with storages from earlier Instiki
|
||||
versions
|
||||
- Some other bug fixes
|
||||
------------------------------------------------------------------------------
|
||||
Upgraded Rails to 0.12.0
|
||||
Upgraded rubyzip to version 0.5.8
|
||||
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)
|
||||
Fixed HTML export (to enclose the output in <html> tags, include the stylesheet etc)
|
||||
Corrected some compatibility issues with storages from earlier Instiki versions
|
||||
Some other bug fixes
|
||||
|
||||
* 0.10.0:
|
||||
- Ported to ActionPack
|
||||
- RedCloth 3.0.3
|
||||
- BlueCloth is phased out, Markdown is rendered by RedCloth
|
||||
- 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
|
||||
./public directory
|
||||
- Much friendlier admin interface
|
||||
- Wiki link syntax doesn't conflict with Textile hyperlink syntax.
|
||||
Therefore "textile link":LinkToSomePlace will not look insane.
|
||||
- RSS feeds accept query parameters, such as
|
||||
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
|
||||
follows: if the web is published, RSS feed links to the published version
|
||||
of the web. otherwise, the feed is not available
|
||||
Madeleine will check every hour if there are new commands in the log or
|
||||
24 hours have passed since last snapshot, and take snapshot if either of
|
||||
these conditions is true. Madeleine will also not log read-only
|
||||
operations, resulting in a better performance
|
||||
- Wiki extracts (to HTML and plain text) will leave only the last extract
|
||||
file in ./storage
|
||||
- Wiki search handles multibyte (UTF-8) characters correctly
|
||||
- Local hyperlinks in published pages point to published pages [Michael
|
||||
DeHaan]
|
||||
- Fixed a bug that sometimes caused all past revisions of a page to be
|
||||
"forgotten" on restart
|
||||
- Fixed parsing of URIs with a port number (http://someplace.org:8080)
|
||||
- Instiki will not fork itself on a *nix, unless explicitly asked to
|
||||
- Instiki can bind to IPs other than 127.0.0.1 (command-line option)
|
||||
- Revisions that do not change anything on the page are rejected
|
||||
- Automated tests for all controller actions
|
||||
- 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
|
||||
------------------------------------------------------------------------------
|
||||
Ported to ActionPack
|
||||
RedCloth 3.0.3
|
||||
BlueCloth is phased out, Markdown is rendered by RedCloth
|
||||
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 ./public
|
||||
directory
|
||||
Much friendlier admin interface
|
||||
Wiki link syntax doesn't conflict with Textile hyperlink syntax. Therefore
|
||||
"textile link":LinkToSomePlace will not look insane.
|
||||
RSS feeds accept query parameters, sush as
|
||||
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 follows:
|
||||
if the web is published, RSS feed links to the published version of the web
|
||||
otherwise, the feed is not available
|
||||
Madeleine will check every hour if there are new commands in the log or 24 hours have
|
||||
passed since last snapshot, and take snapshot if either of these conditions is true
|
||||
Madeleine will also not log read-only operations, resulting in a better performance
|
||||
Wiki extracts (to HTML and plain text) will leave only the last extract file in ./storage
|
||||
Wiki search handles multibyte (UTF-8) characters correctly
|
||||
Local hyperlinks in published pages point to published pages [Michael DeHaan]
|
||||
Fixed a bug that sometimes caused all past revisions of a page to be "forgotten" on
|
||||
restart
|
||||
Fixed parsing of URIs with a port number (http://someplace.org:8080)
|
||||
Instiki will not fork itself on a *nix, unless explicitly asked to
|
||||
Instiki can bind to IPs other than 127.0.0.1 (command-line option)
|
||||
Revisions that do not change anything on the page are rejected
|
||||
Automated tests for all controller actions
|
||||
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:
|
||||
- Rollback takes the user to an edit form. The form has to be submitted for
|
||||
the change to take place.
|
||||
- Changed to use inline style on published pages
|
||||
- Fixed "forward in time" on the last revision before current page
|
||||
- Instiki won't log bogus error messages when creating a new Wiki
|
||||
- Fixed deprecation warning for Object.id (introduced in Ruby 1.8.2)
|
||||
- Madeleine upgraded to 0.7.1
|
||||
- Madeleine snapshots are compressed
|
||||
- Packaged as a gem
|
||||
------------------------------------------------------------------------------
|
||||
Rollback takes the user to an edit form. The form has to be submitted for the change to
|
||||
take place.
|
||||
Changed to use inline style on published pages
|
||||
Fixed "forward in time" on the last revision before current page
|
||||
Instiki won't log bogus error messages when creating a new Wiki
|
||||
Fixed deprecation warning for Object.id (introduced in Ruby 1.8.2)
|
||||
Madeleine upgraded to 0.7.1
|
||||
Madeleine snapshots are compressed
|
||||
Packaged as a gem
|
||||
|
||||
* 0.9.1:
|
||||
- Added performance improvements for updating existing pages
|
||||
- Fixed IP logging and RSS feeds behind proxies [With help from Guan Yang]
|
||||
- Fixed default storage directory (borked running on Windows)
|
||||
[Spotted by Curt Hibbs]
|
||||
------------------------------------------------------------------------------
|
||||
Added performance improvements for updating existing pages
|
||||
Fixed IP logging and RSS feeds behind proxies [With help from Guan Yang]
|
||||
Fixed default storage directory (borked running on Windows) [Spotted by Curt Hibbs]
|
||||
|
||||
* 0.9.0:
|
||||
- Added aliased links such as [[HomePage|that nice home page]] [Mark Reid]
|
||||
- Added include other page content with [[!include TableOfContents]]
|
||||
[Mark Reid]
|
||||
- Added delete orphan pages from the Edit Web screen [by inspiration from
|
||||
Simon Arnaud]
|
||||
- Added logging of IP address for authors (who's behind the rollback wars)
|
||||
- Added Categories pages through backlinks (use "categories: news, instiki"
|
||||
on start of line) [Mark Reid]
|
||||
- Added option to use bracket-style wiki links only (and hence ban
|
||||
WikiWords)
|
||||
- Added command-line option to specify different storage path
|
||||
- Added print view without navigation
|
||||
- Added character and page (2275 characters including spaces) counter
|
||||
(important for student papers)
|
||||
- Off by default, activate it on the Edit Web screen
|
||||
- Added LaTeX/PDF integration on Textile installations with pdflatex
|
||||
installed on system (EXPERIMENTAL)
|
||||
- Use the home page as a table of contents with a unordered list to control
|
||||
sections
|
||||
- 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]
|
||||
------------------------------------------------------------------------------
|
||||
Added aliased links such as [[HomePage|that nice home page]] [Mark Reid]
|
||||
Added include other page content with [[!include TableOfContents]] [Mark Reid]
|
||||
Added delete orphan pages from the Edit Web screen [by inspiration from Simon Arnaud]
|
||||
Added logging of IP address for authors (who's behind the rollback wars)
|
||||
Added Categories pages through backlinks (use "categories: news, instiki" on start of line) [Mark Reid]
|
||||
Added option to use bracket-style wiki links only (and hence ban WikiWords)
|
||||
Added command-line option to specify different storage path
|
||||
Added print view without navigation
|
||||
Added character and page (2275 characters including spaces) counter (important for student papers)
|
||||
Off by default, activate it on the Edit Web screen
|
||||
Added LaTeX/PDF integration on Textile installations with pdflatex installed on system (EXPERIMENTAL)
|
||||
Use the home page as a table of contents with a unordered list to control sections
|
||||
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:
|
||||
- Added color diffs to see changes between revisions [Bill Atkins]
|
||||
They're aren't quite perfect yet as new paragraphs split the <ins> tags
|
||||
(hence 0.8.9, not 0.9.0)
|
||||
- Added redirect to edit if content of page generates an error
|
||||
(so the page doesn't become unusable on bugs in the markup engines)
|
||||
- Fixed update Web with different address bug [Denis Metz]
|
||||
- Fixed a bunch of wiki word rendering issues by doing wiki word detection
|
||||
and replacment at once
|
||||
- Upgraded to BlueCloth 0.0.3b (should fix loads of problems on Markdown
|
||||
wikis)
|
||||
------------------------------------------------------------------------------
|
||||
Added color diffs to see changes between revisions [Bill Atkins]
|
||||
They're aren't quite perfect yet as new paragraphs split the <ins> tags (hence 0.8.9, not 0.9.0)
|
||||
Added redirect to edit if content of page generates an error
|
||||
(so the page doesn't become unusable on bugs in the markup engines)
|
||||
Fixed update Web with different address bug [Denis Metz]
|
||||
Fixed a bunch of wiki word rendering issues by doing wiki word detection and replacment at once
|
||||
Upgraded to BlueCloth 0.0.3b (should fix loads of problems on Markdown wikis)
|
||||
|
||||
* 0.8.5:
|
||||
- Instiki can now serve as a CMS by running a password-protected web with a
|
||||
published front
|
||||
- Added version check at startup (Instiki needs Ruby 1.8.1)
|
||||
------------------------------------------------------------------------------
|
||||
Instiki can now serve as a CMS by running a password-protected web with a published front
|
||||
Added version check at startup (Instiki needs Ruby 1.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:
|
||||
- NOTE: Single-web wikis created in versions prior to 0.8.0 have "instiki"
|
||||
as their system password
|
||||
- Accepts wiki words in bracket style.
|
||||
Examples: [[wiki word]], [[c]], [[We could'nt have done it!]]
|
||||
- Accepts camel-case wiki words in all latin, greek, cyrillian, and
|
||||
armenian unicode characters
|
||||
- Many thanks to Guan Yang for building the higher- and lower-case lookup
|
||||
tables. And thanks to Simon Arnaud for the initial patch that got the
|
||||
work started
|
||||
- Changed charset to UTF-8
|
||||
- Cut down on command-line options and replaced them with an per-web config
|
||||
screen
|
||||
- Added option to extend the stylesheet on a per-web basis to tweak the
|
||||
look in details
|
||||
- Added simple color options for variety
|
||||
- Added option to add/remove password protection on each web
|
||||
- Added the wiki name of the author locking a given page (instead of just
|
||||
"someone")
|
||||
- Removed single/multi-web distinction -- all Instikis are now multi-web
|
||||
- Load libraries from an unshifted load path, so that old installed
|
||||
libraries doesn't clash [Emiel van de Laar]
|
||||
- Keeps the author cookie forever, so you don't have to enter your name
|
||||
again and again
|
||||
- Fixed XHTML so it validates [Bruce D'Arcus]
|
||||
- Authors are no longer listed under orphan pages
|
||||
- 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)
|
||||
------------------------------------------------------------------------------
|
||||
NOTE: Single-web wikis created in versions prior to 0.8.0 have "instiki" as their system password
|
||||
Accepts wiki words in bracket style. Ex: [[wiki word]], [[c]], [[We could'nt have done it!]]
|
||||
Accepts camel-case wiki words in all latin, greek, cyrillian, and armenian unicode characters
|
||||
Many thanks to Guan Yang for building the higher- and lower-case lookup tables
|
||||
And thanks to Simon Arnaud for the initial patch that got the work started
|
||||
Changed charset to UTF-8
|
||||
Cut down on command-line options and replaced them with an per-web config screen
|
||||
Added option to extend the stylesheet on a per-web basis to tweak the look in details
|
||||
Added simple color options for variety
|
||||
Added option to add/remove password protection on each web
|
||||
Added the wiki name of the author locking a given page (instead of just "someone")
|
||||
Removed single/multi-web distinction -- all Instikis are now multi-web
|
||||
Load libraries from an unshifted load path, so that old installed libraries doesn't clash [Emiel van de Laar]
|
||||
Keeps the author cookie forever, so you don't have to enter your name again and again
|
||||
Fixed XHTML so it validates [Bruce D'Arcus]
|
||||
Authors are no longer listed under orphan pages
|
||||
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:
|
||||
- Added Markdown (BlueCloth) and RDoc [Mauricio Fern?ndez] as command-line
|
||||
markup choices
|
||||
- Added wanted and orphan page lists to All pages (only show up if there's
|
||||
actually orphan or wanted pages)
|
||||
- Added ISO-8859-1 as XML encoding in RSS feeds (makes FeedReader among
|
||||
others happy for special entities)
|
||||
- Added proper links in the RSS feed (but the body links are still
|
||||
relative, which NNW and others doesn't grok)
|
||||
- Added access keys: E => Edit, H => HomePage, A => All Pages,
|
||||
U => Recently Revised, X => Export
|
||||
- Added password-login through URL (so you can subscribe to feed on a
|
||||
protected web)
|
||||
- Added web passwords to the feed links for protected webs, so they work
|
||||
without manual login
|
||||
- Added the web name in small letters above all pages within a web
|
||||
- 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
|
||||
------------------------------------------------------------------------------
|
||||
Added Markdown (BlueCloth) and RDoc [Mauricio Fern‡ndez] as command-line markup choices
|
||||
Added wanted and orphan page lists to All pages (only show up if there's actually orphan or wanted pages)
|
||||
Added ISO-8859-1 as XML encoding in RSS feeds (makes FeedReader among others happy for special entities)
|
||||
Added proper links in the RSS feed (but the body links are still relative, which NNW and others doesn't grok)
|
||||
Added access keys: E => Edit, H => HomePage, A => All Pages, U => Recently Revised, X => Export
|
||||
Added password-login through URL (so you can subscribe to feed on a protected web)
|
||||
Added web passwords to the feed links for protected webs, so they work without manual login
|
||||
Added the web name in small letters above all pages within a web
|
||||
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:
|
||||
- Fixed Windows compatibility [Florian]
|
||||
- Fixed bug that would prevent Madeleine from taking snapshots in Daemon
|
||||
mode
|
||||
- Added export entire web as HTML in a zip file
|
||||
- Added RSS feeds
|
||||
- Added proper getops support for the growing number of options [Florian]
|
||||
- Added safe mode that forbids style options in RedCloth [Florian]
|
||||
- Updated RedCloth to 2.0.5
|
||||
------------------------------------------------------------------------------
|
||||
Fixed Windows compatibility [Florian]
|
||||
Fixed bug that would prevent Madeleine from taking snapshots in Daemon mode
|
||||
Added export entire web as HTML in a zip file
|
||||
Added RSS feeds
|
||||
Added proper getops support for the growing number of options [Florian]
|
||||
Added safe mode that forbids style options in RedCloth [Florian]
|
||||
Updated RedCloth to 2.0.5
|
||||
|
||||
* 0.5.0:
|
||||
- NOTE: 0.5.0 is NOT compatible with databases from earlier versions
|
||||
- Added revisions
|
||||
- Added multiple webs
|
||||
- Added password protection for webs on multi-web setups
|
||||
- Added the notion of authors (that are saved in a cookie)
|
||||
- Added command-line option for not running as a Daemon on Unix
|
||||
------------------------------------------------------------------------------
|
||||
NOTE: 0.5.0 is NOT compatible with databases from earlier versions
|
||||
Added revisions
|
||||
Added multiple webs
|
||||
Added password protection for webs on multi-web setups
|
||||
Added the notion of authors (that are saved in a cookie)
|
||||
Added command-line option for not running as a Daemon on Unix
|
||||
|
||||
* 0.3.1:
|
||||
- Added option to escape wiki words with \
|
||||
------------------------------------------------------------------------------
|
||||
Added option to escape wiki words with \
|
||||
|
||||
* 0.3.0:
|
||||
- Brought all files into common style (including Textile help on the edit
|
||||
page)
|
||||
- Added page locking (if someone already is editing a page there's a
|
||||
warning)
|
||||
- Added daemon abilities on Unix (keep Instiki running after you close the
|
||||
terminal)
|
||||
- Made port 2500 the default port, so Instiki can be launched by
|
||||
dobbelt-click
|
||||
- Added Textile cache to speed-up rendering of large pages
|
||||
- Made WikiWords look like "Wiki Words"
|
||||
- Updated RedCloth to 2.0.4
|
||||
------------------------------------------------------------------------------
|
||||
Brought all files into common style (including Textile help on the edit page)
|
||||
Added page locking (if someone already is editing a page there's a warning)
|
||||
Added daemon abilities on Unix (keep Instiki running after you close the terminal)
|
||||
Made port 2500 the default port, so Instiki can be launched by 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:
|
||||
- Upgraded to RedCloth 2.0.2 and Madeleine 0.6.1, which means the
|
||||
- Windows problems are gone. Also fixed a problem with wikiwords
|
||||
- that used part of other wikiwords.
|
||||
------------------------------------------------------------------------------
|
||||
Upgraded to RedCloth 2.0.2 and Madeleine 0.6.1, which means the
|
||||
Windows problems are gone. Also fixed a problem with wikiwords
|
||||
that used part of other wikiwords.
|
||||
|
||||
* 0.2.0:
|
||||
- First public release
|
||||
First public release
|
||||
|
|
12
Gemfile
12
Gemfile
|
@ -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"
|
142
README
Normal file → Executable file
142
README
Normal file → Executable 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 it’s 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.
|
||||
Step 2. Run "instiki"
|
||||
|
||||
Step 3. Chuckle... "There's no step three!" (TM)
|
||||
|
||||
== Supported Platforms
|
||||
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.
|
||||
|
||||
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 3. Chuckle... "There's no step three!" (TM)
|
||||
|
||||
|
||||
== Details
|
||||
|
||||
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.
|
||||
|
||||
|
||||
=== 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
|
||||
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.
|
||||
|
||||
===Features:
|
||||
* Regular expression search: Find deep stuff really fast
|
||||
* 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
|
||||
|
@ -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
|
||||
* 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?
|
||||
* Five markup choices:
|
||||
Markdown-based choices [http://daringfireball.net/projects/markdown/syntax]:
|
||||
Markdown+itex2MML (the default; requires itex2MML)
|
||||
Markdown+BlahTeX/PNG (requires blahtex and a working TeX installation)
|
||||
Markdown
|
||||
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).
|
||||
* Speed: Using Madelein[http://madeleine.sourceforge.net] for persistence (all pages are in memory)
|
||||
* Three markup choices: Textile[http://www.textism.com/tools/textile]
|
||||
(default / RedCloth[http://www.whytheluckystiff.net/ruby/redcloth]),
|
||||
Markdown (BlueCloth[http://bluecloth.rubyforge.org]), and RDoc[http://rdoc.sourceforge.net/doc]
|
||||
* Embedded webserver: Through WEBrick[http://www.webrick.org]
|
||||
* Internationalization: Wiki words in any latin, greek, cyrillian, or armenian characters
|
||||
* 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
|
||||
|
||||
== Migrating from Instiki 0.11-0.18 to 0.19
|
||||
|
||||
ruby bundle
|
||||
ruby bundle exec rake upgrade_instiki
|
||||
|
||||
== Download the latest release from:
|
||||
|
||||
===Download latest from:
|
||||
* http://rubyforge.org/project/showfiles.php?group_id=186
|
||||
|
||||
===Visit the official Instiki wiki:
|
||||
* http://www.instiki.org
|
||||
|
||||
== Visit the Instiki wiki:
|
||||
|
||||
* http://golem.ph.utexas.edu/wiki/instiki/
|
||||
|
||||
|
||||
== License:
|
||||
|
||||
===License:
|
||||
* same as Ruby's
|
||||
|
||||
|
||||
---
|
||||
|
||||
Authors::
|
||||
|
||||
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
|
||||
Author:: David Heinemeier Hansson
|
||||
Email:: david@loudthinking.com
|
||||
Weblog:: http://www.loudthinking.com
|
||||
|
|
21
UPGRADING
21
UPGRADING
|
@ -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
0
app/apis/.gitignore
vendored
|
@ -1,8 +1,8 @@
|
|||
require 'application'
|
||||
|
||||
class AdminController < ApplicationController
|
||||
|
||||
layout 'default'
|
||||
cache_sweeper :web_sweeper
|
||||
before_filter :dnsbl_check
|
||||
|
||||
def create_system
|
||||
if @wiki.setup?
|
||||
|
@ -12,12 +12,12 @@ class AdminController < ApplicationController
|
|||
"\n\n" +
|
||||
"(WARNING: this will destroy content of your current wiki)."
|
||||
redirect_home(@wiki.webs.keys.first)
|
||||
elsif params['web_name']
|
||||
elsif @params['web_name']
|
||||
# form submitted -> create a wiki
|
||||
@wiki.setup(params['password'], params['web_name'], params['web_address'])
|
||||
flash[:info] = "Your new wiki '#{params['web_name']}' is created!\n" +
|
||||
@wiki.setup(@params['password'], @params['web_name'], @params['web_address'])
|
||||
flash[:info] = "Your new wiki '#{@params['web_name']}' is created!\n" +
|
||||
"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'
|
||||
else
|
||||
# no form submitted -> go to template
|
||||
|
@ -25,22 +25,20 @@ class AdminController < ApplicationController
|
|||
end
|
||||
|
||||
def create_web
|
||||
if params['address']
|
||||
return unless is_post
|
||||
if @params['address']
|
||||
# form submitted
|
||||
if @wiki.authenticate(params['system_password'])
|
||||
if @wiki.authenticate(@params['system_password'])
|
||||
begin
|
||||
@wiki.create_web(params['name'], params['address'])
|
||||
flash[:info] = "New web '#{params['name']}' successfully created."
|
||||
redirect_to :web => params['address'], :controller => 'wiki', :action => 'new',
|
||||
@wiki.create_web(@params['name'], @params['address'])
|
||||
flash[:info] = "New web '#{@params['name']}' successfully created."
|
||||
redirect_to :web => @params['address'], :controller => 'wiki', :action => 'new',
|
||||
:id => 'HomePage'
|
||||
rescue Instiki::ValidationError => e
|
||||
@error = e.message
|
||||
# and re-render the form again
|
||||
end
|
||||
else
|
||||
flash[:error] = "System Password incorrect. Try again."
|
||||
redirect_to :controller => 'admin', :action => 'create_web'
|
||||
redirect_to :controller => 'wiki', :action => 'index'
|
||||
end
|
||||
else
|
||||
# no form submitted -> render template
|
||||
|
@ -48,30 +46,27 @@ class AdminController < ApplicationController
|
|||
end
|
||||
|
||||
def edit_web
|
||||
system_password = params['system_password']
|
||||
|
||||
system_password = @params['system_password']
|
||||
if system_password
|
||||
return unless is_post
|
||||
# form submitted
|
||||
if wiki.authenticate(system_password)
|
||||
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(
|
||||
@web.address, params['address'], params['name'],
|
||||
params['markup'].intern,
|
||||
params['color'], params['additional_style'],
|
||||
params['safe_mode'] ? true : false,
|
||||
params['password'].empty? ? nil : params['password'],
|
||||
params['published'] ? true : false,
|
||||
params['brackets_only'] ? true : false,
|
||||
params['count_pages'] ? true : false,
|
||||
params['allow_uploads'] ? true : false,
|
||||
params['max_upload_size']
|
||||
@web.address, @params['address'], @params['name'],
|
||||
@params['markup'].intern,
|
||||
@params['color'], @params['additional_style'],
|
||||
@params['safe_mode'] ? true : false,
|
||||
@params['password'].empty? ? nil : @params['password'],
|
||||
@params['published'] ? true : false,
|
||||
@params['brackets_only'] ? true : false,
|
||||
@params['count_pages'] ? true : false,
|
||||
@params['allow_uploads'] ? true : false,
|
||||
@params['max_upload_size']
|
||||
)
|
||||
flash[:info] = "Web '#{params['address']}' was successfully updated"
|
||||
redirect_home(params['address'])
|
||||
flash[:info] = "Web '#{@params['address']}' was successfully updated"
|
||||
redirect_home(@params['address'])
|
||||
rescue Instiki::ValidationError => e
|
||||
logger.warn e.message
|
||||
@error = e.message
|
||||
# and re-render the same template again
|
||||
end
|
||||
|
@ -85,57 +80,14 @@ class AdminController < ApplicationController
|
|||
end
|
||||
|
||||
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)
|
||||
flash[:info] = 'Orphaned pages removed'
|
||||
redirect_to :controller => 'wiki', :web => @web_name, :action => 'list'
|
||||
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'
|
||||
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
|
||||
|
|
161
app/controllers/application.rb
Normal file
161
app/controllers/application.rb
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -1,69 +1,29 @@
|
|||
# Controller responsible for serving files and pictures.
|
||||
require 'fileutils'
|
||||
require 'application'
|
||||
require 'instiki_errors'
|
||||
|
||||
require 'zip/zip'
|
||||
require 'instiki_stringsupport'
|
||||
# Controller that is responsible for serving files and pictures.
|
||||
# Disabled in version 0.10
|
||||
|
||||
class FileController < ApplicationController
|
||||
|
||||
layout 'default'
|
||||
|
||||
before_filter :check_authorized
|
||||
before_filter :check_allow_uploads, :dnsbl_check, :except => [:file, :blahtex_png]
|
||||
before_filter :check_allow_uploads
|
||||
|
||||
def file
|
||||
@file_name = params['id']
|
||||
if params['file']
|
||||
return unless is_post and check_allow_uploads
|
||||
check_path
|
||||
if @params['file']
|
||||
# form supplied
|
||||
new_file = @web.wiki_files.create(params['file'])
|
||||
if new_file.valid?
|
||||
flash[:info] = "File '#{@file_name}' successfully uploaded"
|
||||
WikiReference.pages_that_link_to_file(@web, @file_name).each do |page|
|
||||
RevisionSweeper.expire_page(@web, page)
|
||||
end
|
||||
redirect_to(params['referring_page'])
|
||||
else
|
||||
# pass the file with errors back into the form
|
||||
@file = new_file
|
||||
render
|
||||
end
|
||||
file_yard.upload_file(@file_name, @params['file'])
|
||||
flash[:info] = "File '#{@file_name}' successfully uploaded"
|
||||
@web.refresh_pages_with_references(@file_name)
|
||||
return_to_last_remembered
|
||||
elsif file_yard.has_file?(@file_name)
|
||||
send_file(file_yard.file_path(@file_name))
|
||||
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
|
||||
logger.debug("File not found: #{file_yard.files_path}/#{@file_name}")
|
||||
# go to the template, which is a file upload form
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -71,15 +31,35 @@ class FileController < ApplicationController
|
|||
return_to_last_remembered
|
||||
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
|
||||
if params['file']
|
||||
return if file_uploads_disabled?
|
||||
|
||||
check_authorization
|
||||
if @params['file']
|
||||
@problems = []
|
||||
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?
|
||||
flash[:info] = 'Import successfully finished'
|
||||
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>'
|
||||
end
|
||||
return_to_last_remembered
|
||||
|
@ -90,36 +70,40 @@ class FileController < ApplicationController
|
|||
|
||||
protected
|
||||
|
||||
def check_authorized
|
||||
unless authorized? or @web.published?
|
||||
@hide_navigation = true
|
||||
render(:status => 403, :text => 'This web is private', :layout => true)
|
||||
def check_allow_uploads
|
||||
|
||||
# TODO enable file uploads again after 0.10 release
|
||||
unless RAILS_ENV == 'test'
|
||||
render_text 'File uploads are not ready for general use in Instiki 0.10', '403 Forbidden'
|
||||
return false
|
||||
end
|
||||
|
||||
unless @web.allow_uploads
|
||||
render_text 'File uploads are blocked by the webmaster', '403 Forbidden'
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def check_allow_uploads
|
||||
if @web
|
||||
if @web.allow_uploads? and authorized?
|
||||
return true
|
||||
else
|
||||
@hide_navigation = true
|
||||
render(:status => 403, :text => 'File uploads are blocked by the webmaster', :layout => true)
|
||||
return false
|
||||
end
|
||||
else
|
||||
render(:status => 404, :text => "Web #{params['web'].inspect} not found", :layout => 'error')
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
logger.info "Importing pages from #{archive}"
|
||||
zip = Zip::ZipInputStream.open(archive)
|
||||
while (entry = zip.get_next_entry) do
|
||||
ext_length = File.extname(entry.name).length
|
||||
page_name = entry.name[0..-(ext_length + 1)].purify
|
||||
page_content = entry.get_input_stream.read.purify
|
||||
page_name = entry.name[0..-(ext_length + 1)]
|
||||
page_content = entry.get_input_stream.read
|
||||
logger.info "Processing page '#{page_name}'"
|
||||
begin
|
||||
existing_page = @wiki.read_page(@web.address, page_name)
|
||||
|
@ -129,10 +113,10 @@ class FileController < ApplicationController
|
|||
next
|
||||
else
|
||||
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
|
||||
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
|
||||
rescue => e
|
||||
logger.error(e)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,26 +1,18 @@
|
|||
require 'application'
|
||||
require 'fileutils'
|
||||
require 'maruku'
|
||||
require 'maruku/ext/math'
|
||||
require 'zip/zip'
|
||||
require 'instiki_stringsupport'
|
||||
require 'resolv'
|
||||
require 'redcloth_for_tex'
|
||||
require 'parsedate'
|
||||
|
||||
class WikiController < ApplicationController
|
||||
|
||||
before_filter :load_page
|
||||
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]
|
||||
layout 'default', :except => [:rss_feed, :rss_with_content, :rss_with_headlines, :tex, :export_tex, :export_html]
|
||||
|
||||
def index
|
||||
if @web_name
|
||||
redirect_home
|
||||
elsif not @wiki.setup?
|
||||
redirect_to :controller => 'admin', :action => 'create_system'
|
||||
elsif @wiki.webs.size == 1
|
||||
elsif @wiki.webs.length == 1
|
||||
redirect_home @wiki.webs.values.first.address
|
||||
else
|
||||
redirect_to :action => 'web_list'
|
||||
|
@ -30,10 +22,10 @@ class WikiController < ApplicationController
|
|||
# Outside a single web --------------------------------------------------------
|
||||
|
||||
def authenticate
|
||||
if password_check(params['password'])
|
||||
if password_check(@params['password'])
|
||||
redirect_home
|
||||
else
|
||||
flash[:info] = password_error(params['password'])
|
||||
flash[:info] = password_error(@params['password'])
|
||||
redirect_to :action => 'login', :web => @web_name
|
||||
end
|
||||
end
|
||||
|
@ -50,84 +42,14 @@ class WikiController < ApplicationController
|
|||
# Within a single web ---------------------------------------------------------
|
||||
|
||||
def authors
|
||||
@page_names_by_author = @web.page_names_by_author
|
||||
@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)
|
||||
@authors = @web.select.authors.sort
|
||||
end
|
||||
|
||||
def export_html
|
||||
export_pages_as_zip(html_ext) do |page|
|
||||
renderer = PageRenderer.new(page.current_revision)
|
||||
rendered_page = <<-EOL
|
||||
<!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} 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
|
||||
export_pages_as_zip('html') do |page|
|
||||
@page = page
|
||||
@link_mode = :export
|
||||
render_to_string('wiki/print', use_layout = (@params['layout'] != 'no'))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -135,21 +57,21 @@ EOL
|
|||
export_pages_as_zip(@web.markup) { |page| page.content }
|
||||
end
|
||||
|
||||
# def export_pdf
|
||||
# file_name = "#{@web.address}-tex-#{@web.revised_at.strftime('%Y-%m-%d-%H-%M-%S')}"
|
||||
# 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_pdf
|
||||
file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}"
|
||||
file_path = File.join(@wiki.storage_path, file_name)
|
||||
|
||||
# def export_tex
|
||||
# file_name = "#{@web.address}-tex-#{@web.revised_at.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
|
||||
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
|
||||
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
|
||||
@rss_with_content_allowed = rss_with_content_allowed?
|
||||
|
@ -158,6 +80,7 @@ EOL
|
|||
|
||||
def list
|
||||
parse_category
|
||||
@pages_by_name = @pages_in_category.by_name
|
||||
@page_names_that_are_wanted = @pages_in_category.wanted_pages
|
||||
@pages_that_are_orphaned = @pages_in_category.orphaned_pages
|
||||
end
|
||||
|
@ -165,53 +88,23 @@ EOL
|
|||
def recently_revised
|
||||
parse_category
|
||||
@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
|
||||
|
||||
def atom_with_content
|
||||
def rss_with_content
|
||||
if rss_with_content_allowed?
|
||||
render_atom(hide_description = false)
|
||||
render_rss(hide_description = false, *parse_rss_params)
|
||||
else
|
||||
render :text => 'Atom feed with content for this web is blocked for security reasons. ' +
|
||||
'The web is password-protected and not published', :status => 403, :layout => 'error'
|
||||
render_text 'RSS feed with content for this web is blocked for security reasons. ' +
|
||||
'The web is password-protected and not published', '403 Forbidden'
|
||||
end
|
||||
end
|
||||
|
||||
def atom_with_headlines
|
||||
render_atom(hide_description = true)
|
||||
def rss_with_headlines
|
||||
render_rss(hide_description = true, *parse_rss_params)
|
||||
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
|
||||
@query = params['query'] ? params['query'].purify : ''
|
||||
@query = @params['query']
|
||||
@title_results = @web.select { |page| page.name =~ /#{@query}/i }.sort
|
||||
@results = @web.select { |page| page.content =~ /#{@query}/i }.sort
|
||||
all_pages_found = (@results + @title_results).uniq
|
||||
|
@ -230,7 +123,7 @@ EOL
|
|||
def edit
|
||||
if @page.nil?
|
||||
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
|
||||
else
|
||||
@page.lock(Time.now, @author)
|
||||
|
@ -245,102 +138,64 @@ EOL
|
|||
# to template
|
||||
end
|
||||
|
||||
# def pdf
|
||||
# page = wiki.read_page(@web_name, @page_name)
|
||||
# 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_path = File.join(@wiki.storage_path, file_name)
|
||||
#
|
||||
# export_page_to_tex("#{file_path}.tex") unless FileTest.exists?("#{file_path}.tex")
|
||||
# # NB: this is _very_ slow
|
||||
# convert_tex_to_pdf("#{file_path}.tex")
|
||||
# send_file "#{file_path}.pdf"
|
||||
# end
|
||||
def pdf
|
||||
page = wiki.read_page(@web_name, @page_name)
|
||||
safe_page_name = @page.name.gsub(/\W/, '')
|
||||
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)
|
||||
|
||||
export_page_to_tex("#{file_path}.tex") unless FileTest.exists?("#{file_path}.tex")
|
||||
# NB: this is _very_ slow
|
||||
convert_tex_to_pdf("#{file_path}.tex")
|
||||
send_file "#{file_path}.pdf"
|
||||
end
|
||||
|
||||
def print
|
||||
if @page.nil?
|
||||
redirect_home
|
||||
end
|
||||
@link_mode ||= :show
|
||||
@renderer = PageRenderer.new(@page.current_revision)
|
||||
# to template
|
||||
end
|
||||
|
||||
def published
|
||||
if not @web.published?
|
||||
render(:text => "Published version of web '#{@web_name}' is not available", :status => 404, :layout => 'error')
|
||||
return
|
||||
end
|
||||
|
||||
@page_name ||= 'HomePage'
|
||||
@page ||= wiki.read_page(@web_name, @page_name)
|
||||
@link_mode ||= :publish
|
||||
if @page
|
||||
@renderer = PageRenderer.new(@page.current_revision)
|
||||
if @web.published
|
||||
@page = wiki.read_page(@web_name, @page_name || 'HomePage')
|
||||
else
|
||||
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 => 'published', :id => real_page, :status => 301
|
||||
else
|
||||
render(:text => "Page '#{@page_name}' not found", :status => 404, :layout => 'error')
|
||||
end
|
||||
end
|
||||
redirect_home
|
||||
end
|
||||
end
|
||||
|
||||
def revision
|
||||
get_page_and_revision
|
||||
@show_diff = (params[:mode] == 'diff')
|
||||
@renderer = PageRenderer.new(@revision)
|
||||
end
|
||||
|
||||
def rollback
|
||||
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
|
||||
|
||||
def save
|
||||
render(:status => 404, :text => 'Undefined page name', :layout => 'error') and return if @page_name.nil?
|
||||
return unless is_post
|
||||
author_name = params['author'].purify
|
||||
author_name = 'AnonymousCoward' if author_name =~ /^\s*$/
|
||||
redirect_home if @page_name.nil?
|
||||
cookies['author'] = @params['author']
|
||||
|
||||
begin
|
||||
the_content = params['content'].purify
|
||||
prev_content = ''
|
||||
filter_spam(the_content)
|
||||
cookies['author'] = { :value => author_name.dup.as_bytes, :expires => Time.utc(2030) }
|
||||
check_for_spam(@params['content'], remote_ip)
|
||||
check_blocked_ips(remote_ip)
|
||||
|
||||
if @page
|
||||
new_name = params['new_name'] ? params['new_name'].purify : @page_name
|
||||
new_name = @page_name if new_name.empty?
|
||||
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)
|
||||
wiki.revise_page(@web_name, @page_name, @params['content'], Time.now,
|
||||
Author.new(@params['author'], remote_ip))
|
||||
@page.unlock
|
||||
@page_name = new_name
|
||||
else
|
||||
wiki.write_page(@web_name, @page_name, the_content, Time.now,
|
||||
Author.new(author_name, remote_ip), PageRenderer.new)
|
||||
wiki.write_page(@web_name, @page_name, @params['content'], Time.now,
|
||||
Author.new(@params['author'], remote_ip))
|
||||
end
|
||||
redirect_to_page @page_name
|
||||
rescue Instiki::ValidationError => e
|
||||
flash[:error] = e.to_s
|
||||
logger.error e
|
||||
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
|
||||
rescue => e
|
||||
flash[:error] = e
|
||||
flash[:content] = @params['content']
|
||||
if @page
|
||||
@page.unlock
|
||||
redirect_to param_hash.update( :action => 'edit' )
|
||||
redirect_to :action => 'edit', :web => @web_name, :id => @page_name
|
||||
else
|
||||
redirect_to param_hash.update( :action => 'new' )
|
||||
redirect_to :action => 'new', :web => @web_name, :id => @page_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -348,225 +203,177 @@ EOL
|
|||
def show
|
||||
if @page
|
||||
begin
|
||||
@renderer = PageRenderer.new(@page.current_revision)
|
||||
@show_diff = (params[:mode] == 'diff')
|
||||
render :action => 'page'
|
||||
render_action 'page'
|
||||
# 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)
|
||||
rescue => e
|
||||
logger.error e
|
||||
flash[:error] = e.to_s
|
||||
flash[:error] = e.message
|
||||
if in_a_web?
|
||||
redirect_to :action => 'edit', :web => @web_name, :id => @page_name
|
||||
else
|
||||
raise e
|
||||
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
|
||||
if not @page_name.nil? and not @page_name.empty?
|
||||
redirect_to :web => @web_name, :action => 'new', :id => @page_name
|
||||
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
|
||||
|
||||
def source
|
||||
@revision = @page.revisions[params['rev'].to_i - 1] if params['rev']
|
||||
end
|
||||
|
||||
def tex
|
||||
if [:markdownMML, :markdownPNG, :markdown].include?(@web.markup)
|
||||
@tex_content = Maruku.new(@page.content).to_latex
|
||||
else
|
||||
@tex_content = 'TeX export only supported with the Markdown text filters.'
|
||||
end
|
||||
render(:layout => 'tex')
|
||||
@tex_content = RedClothForTex.new(@page.content).to_tex
|
||||
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
|
||||
|
||||
# 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 check_blocked_ips(ip)
|
||||
if defined? BLOCKED_IPS and not BLOCKED_IPS.nil?
|
||||
BLOCKED_IPS.each do |blocked_ip|
|
||||
raise Instiki::ValidationError.new('Revision rejected by spam filter') if ip == blocked_ip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_for_spam(new_content, ip)
|
||||
if defined? SPAM_PATTERNS and not SPAM_PATTERNS.nil?
|
||||
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)
|
||||
if @web.markup == :markdownMML || @web.markup == :markdownPNG
|
||||
@tex_content = Maruku.new(@page.content).to_latex
|
||||
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')) }
|
||||
tex
|
||||
File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex')) }
|
||||
end
|
||||
|
||||
def export_pages_as_zip(file_type, &block)
|
||||
|
||||
file_prefix = "#{@web.address}-#{file_type}-"
|
||||
timestamp = @web.revised_at.strftime('%Y-%m-%d-%H-%M-%S')
|
||||
file_path = @wiki.storage_path.join(file_prefix + timestamp + '.zip')
|
||||
timestamp = @web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')
|
||||
file_path = File.join(@wiki.storage_path, file_prefix + timestamp + '.zip')
|
||||
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|
|
||||
zip_out.get_output_stream("#{CGI.escape(page.name)}.#{file_type}") do |f|
|
||||
f.puts(block.call(page))
|
||||
end
|
||||
zip_out.put_next_entry("#{CGI.escape(page.name)}.#{file_type}")
|
||||
zip_out.puts(block.call(page))
|
||||
end
|
||||
# add an index file, and the stylesheet and javascript directories, if exporting to HTML
|
||||
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
|
||||
# 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
|
||||
FileUtils.rm_rf(Dir[@wiki.storage_path.join(file_prefix + '*.zip').to_s])
|
||||
FileUtils.rm_rf(Dir[File.join(@wiki.storage_path, file_prefix + '*.zip')])
|
||||
FileUtils.mv(tmp_path, file_path)
|
||||
send_file file_path
|
||||
end
|
||||
|
||||
# def export_web_to_tex(file_path)
|
||||
# if @web.markup == :markdownMML
|
||||
# @tex_content = Maruku.new(@page.content).to_latex
|
||||
# else
|
||||
# @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 export_web_to_tex(file_path)
|
||||
@tex_content = table_of_contents(@web.pages['HomePage'].content, render_tex_web)
|
||||
File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex_web')) }
|
||||
end
|
||||
|
||||
def get_page_and_revision
|
||||
if params['rev']
|
||||
@revision_number = params['rev'].to_i
|
||||
revision_index = (@params['rev'] || 0).to_i
|
||||
if @page.nil? or @page.revisions[revision_index].nil?
|
||||
render_text 'Page not found', '404 Not Found'
|
||||
else
|
||||
@revision_number = @page.rev_ids.size
|
||||
@revision = @page.revisions[revision_index]
|
||||
end
|
||||
@revision = @page.revisions[@revision_number - 1]
|
||||
end
|
||||
|
||||
def parse_category
|
||||
@categories = WikiReference.list_categories(@web).sort
|
||||
@category = params['category']
|
||||
if @category
|
||||
@categories = @web.categories
|
||||
@category = @params['category']
|
||||
if @categories.include?(@category)
|
||||
@pages_in_category = @web.select { |page| page.in_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
|
||||
# no category specified, return all pages of the web
|
||||
@pages_in_category = @web.select_all.by_name
|
||||
@pages_in_category = PageSet.new(@web).by_name
|
||||
@set_name = 'the web'
|
||||
end
|
||||
end
|
||||
|
||||
def remote_ip
|
||||
ip = request.remote_ip
|
||||
logger.info(ip)
|
||||
ip.dup.gsub!(Regexp.union(Resolv::IPv4::Regex, Resolv::IPv6::Regex), '\0') || 'bogus address'
|
||||
def parse_rss_params
|
||||
if @params.include? 'limit'
|
||||
limit = @params['limit'].to_i rescue nil
|
||||
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
|
||||
|
||||
def render_atom(hide_description = false, limit = 15)
|
||||
@pages_by_revision = @web.select.by_revision.first(limit)
|
||||
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)
|
||||
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
|
||||
@response.headers['Content-Type'] = 'text/xml'
|
||||
@link_action = @web.password ? 'published' : 'show'
|
||||
render :action => 'atom'
|
||||
|
||||
render 'wiki/rss_feed'
|
||||
end
|
||||
|
||||
def render_tex_web
|
||||
@web.select.by_name.inject({}) do |tex_web, page|
|
||||
if @web.markup == :markdownMML || @web.markup == :markdownPNG
|
||||
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[page.name] = RedClothForTex.new(page.content).to_tex
|
||||
tex_web
|
||||
end
|
||||
end
|
||||
|
||||
def rss_with_content_allowed?
|
||||
@web.password.nil? or @web.published?
|
||||
end
|
||||
|
||||
def filter_spam(content)
|
||||
@@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) }
|
||||
def render_to_string(template_name, with_layout = false)
|
||||
add_variables_to_assigns
|
||||
self.assigns['content_for_layout'] = @template.render_file(template_name)
|
||||
if with_layout
|
||||
@template.render_file('layouts/default')
|
||||
else
|
||||
[]
|
||||
self.assigns['content_for_layout']
|
||||
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
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# The methods added to this helper will be available to all templates in the application.
|
||||
module ApplicationHelper
|
||||
require 'instiki_stringsupport'
|
||||
|
||||
# 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
|
||||
|
@ -20,18 +19,18 @@ require 'instiki_stringsupport'
|
|||
container = container.to_a if Hash === container
|
||||
|
||||
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
|
||||
options << "<option value=\"#{element.last}\">#{element.first}</option>"
|
||||
else
|
||||
options << "<option value=\"#{element.last}\" selected=\"selected\">#{element.first}</option>"
|
||||
options << "<option value=\"#{element.last}\" selected>#{element.first}</option>"
|
||||
end
|
||||
else
|
||||
options << ((element != selected) ? "<option>#{element}</option>" : "<option selected>#{element}</option>")
|
||||
end
|
||||
end
|
||||
|
||||
html_options.join("\n").html_safe
|
||||
html_options.join("\n")
|
||||
end
|
||||
|
||||
# Creates a hyperlink to a Wiki page, without checking if the page exists or not
|
||||
|
@ -39,39 +38,13 @@ require 'instiki_stringsupport'
|
|||
link_to(
|
||||
text || page.plain_name,
|
||||
{:web => @web.address, :action => 'show', :id => page.name, :only_path => true},
|
||||
html_options).html_safe
|
||||
html_options)
|
||||
end
|
||||
|
||||
# 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 = {})
|
||||
raise 'Web not defined' if web.nil?
|
||||
UrlGenerator.new(@controller).make_link(@web, page_name, web, text,
|
||||
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
|
||||
web.make_link(page_name, text, options.merge(:base_url => "#{base_url}/#{web.address}"))
|
||||
end
|
||||
|
||||
def base_url
|
||||
|
@ -84,47 +57,19 @@ require 'instiki_stringsupport'
|
|||
if @categories.empty?
|
||||
''
|
||||
else
|
||||
("<div id=\"categories\">\n" +
|
||||
"<div id=\"categories\">\n" +
|
||||
'<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|
|
||||
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" +
|
||||
'</div>').html_safe
|
||||
'</div>'
|
||||
end
|
||||
end
|
||||
|
||||
# Performs HTML escaping on text, but keeps linefeeds intact (by replacing them with <br/>)
|
||||
def escape_preserving_linefeeds(text)
|
||||
h(text).gsub(/\n/, '<br/>').as_utf8.html_safe
|
||||
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
|
||||
h(text).gsub(/\n/, '<br/>')
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,18 +1,4 @@
|
|||
class Author < String
|
||||
attr_accessor :ip
|
||||
attr_reader :name
|
||||
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
|
||||
def initialize(name, ip) @ip = ip; super(name) end
|
||||
end
|
33
app/models/chunks/category.rb
Normal file
33
app/models/chunks/category.rb
Normal 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
|
86
app/models/chunks/chunk.rb
Normal file
86
app/models/chunks/chunk.rb
Normal 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
|
61
app/models/chunks/engines.rb
Normal file
61
app/models/chunks/engines.rb
Normal 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
|
41
app/models/chunks/include.rb
Normal file
41
app/models/chunks/include.rb
Normal 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
|
31
app/models/chunks/literal.rb
Normal file
31
app/models/chunks/literal.rb
Normal 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
|
|
@ -1,5 +1,4 @@
|
|||
require 'chunks/chunk'
|
||||
require 'sanitizer'
|
||||
|
||||
# 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
|
||||
|
@ -14,11 +13,8 @@ require 'sanitizer'
|
|||
#
|
||||
# Author: Mark Reid <mark at threewordslong dot com>
|
||||
# Created: 8th June 2004
|
||||
|
||||
class NoWiki < Chunk::Abstract
|
||||
|
||||
include Sanitizer
|
||||
|
||||
NOWIKI_PATTERN = Regexp.new('<nowiki>(.*?)</nowiki>', Regexp::MULTILINE)
|
||||
def self.pattern() NOWIKI_PATTERN end
|
||||
|
||||
|
@ -26,7 +22,7 @@ class NoWiki < Chunk::Abstract
|
|||
|
||||
def initialize(match_data, content)
|
||||
super
|
||||
@plain_text = @unmask_text = safe_xhtml_sanitize(match_data[1])
|
||||
@plain_text = @unmask_text = match_data[1]
|
||||
end
|
||||
|
||||
end
|
18
app/models/chunks/test.rb
Normal file
18
app/models/chunks/test.rb
Normal 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
182
app/models/chunks/uri.rb
Normal 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
141
app/models/chunks/wiki.rb
Normal 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
58
app/models/file_yard.rb
Normal 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
|
|
@ -1,136 +1,120 @@
|
|||
class Page < ActiveRecord::Base
|
||||
belongs_to :web
|
||||
has_many :revisions, :order => 'id', :dependent => :destroy
|
||||
#In many cases, we don't need to instantiate the full revisions (with all that textual data)
|
||||
has_many :rev_ids, :order => 'id', :class_name => 'Revision', :select => 'id, revised_at, page_id, author, ip'
|
||||
has_many :wiki_references, :order => 'referenced_name'
|
||||
has_one :current_revision, :class_name => 'Revision', :order => 'id DESC'
|
||||
require 'date'
|
||||
require 'page_lock'
|
||||
require 'revision'
|
||||
require 'wiki_words'
|
||||
require 'chunks/wiki'
|
||||
|
||||
def name
|
||||
read_attribute(:name).as_utf8
|
||||
class Page
|
||||
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
|
||||
|
||||
def revise(content, name, time, author, renderer)
|
||||
revisions_size = new_record? ? 0 : rev_ids.size
|
||||
if (revisions_size > 0) and content == current_revision.content and name == self.name
|
||||
def revise(content, created_at, author)
|
||||
|
||||
if not @revisions.empty? and content == @revisions.last.content
|
||||
raise Instiki::ValidationError.new(
|
||||
"You have tried to save page '#{name}' without changing its content")
|
||||
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,
|
||||
renderer.revision = Revision.new(
|
||||
:page => self, :content => content, :author => author, :revised_at => time)
|
||||
renderer.display_content(update_references = true)
|
||||
# before addin a revision to the page
|
||||
Revision.new(self, @revisions.length, content, created_at, author).force_rendering
|
||||
|
||||
# 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
|
||||
# by the same author, not more than 30 minutes ago, then update the last revision instead of
|
||||
# creating a new one
|
||||
if (revisions_size > 0) && continous_revision?(time, author)
|
||||
current_revision.update_attributes(:content => content, :revised_at => time)
|
||||
if !@revisions.empty? && continous_revision?(created_at, author)
|
||||
@revisions.last.created_at = created_at
|
||||
@revisions.last.content = content
|
||||
@revisions.last.clear_display_cache
|
||||
else
|
||||
revisions.build(:content => content, :author => author, :revised_at => time)
|
||||
@revisions << Revision.new(self, @revisions.length, content, created_at, author)
|
||||
end
|
||||
save
|
||||
|
||||
self.revisions.last.force_rendering
|
||||
# at this point the page may not be inserted in the web yet, and therefore
|
||||
# references to the page itself are rendered as "unresolved". Clearing the cache allows
|
||||
# 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
|
||||
|
||||
def rollback(revision_number, time, author_ip, renderer)
|
||||
roll_back_revision = self.revisions[revision_number]
|
||||
if roll_back_revision.nil?
|
||||
raise Instiki::ValidationError.new("Revision #{revision_number} not found")
|
||||
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
|
||||
|
||||
def revisions?
|
||||
rev_ids.size > 1
|
||||
revisions.length > 1
|
||||
end
|
||||
|
||||
def previous_revision(revision)
|
||||
revision_index = rev_ids.each_with_index do |rev, index|
|
||||
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
|
||||
def revised_on
|
||||
created_on
|
||||
end
|
||||
|
||||
def references
|
||||
web.select.pages_that_reference(name)
|
||||
end
|
||||
|
||||
def wiki_words
|
||||
wiki_references.select { |ref| ref.wiki_word? }.map { |ref| ref.referenced_name }
|
||||
def in_category?(cat)
|
||||
cat.nil? || cat.empty? || categories.include?(cat)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def linked_from
|
||||
web.select.pages_that_link_to(name)
|
||||
end
|
||||
|
||||
def redirects
|
||||
wiki_references.select { |ref| ref.redirected_page? }.map { |ref| ref.referenced_name }
|
||||
@web.select.pages_that_link_to(name)
|
||||
end
|
||||
|
||||
def included_from
|
||||
web.select.pages_that_include(name)
|
||||
@web.select.pages_that_include(name)
|
||||
end
|
||||
|
||||
# Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page".
|
||||
def plain_name
|
||||
web.brackets_only? ? name.escapeHTML.html_safe : WikiWords.separate(name).escapeHTML.html_safe
|
||||
@web.brackets_only ? name : WikiWords.separate(name)
|
||||
end
|
||||
|
||||
LOCKING_PERIOD = 30.minutes
|
||||
|
||||
def lock(time, locked_by)
|
||||
update_attributes(:locked_at => time, :locked_by => locked_by)
|
||||
# used to build chunk ids.
|
||||
def id
|
||||
@id ||= name.unpack('H*').first
|
||||
end
|
||||
|
||||
def lock_duration(time)
|
||||
((time - locked_at) / 60).to_i unless locked_at.nil?
|
||||
def link(options = {})
|
||||
@web.make_link(name, nil, options)
|
||||
end
|
||||
|
||||
def unlock
|
||||
update_attribute(:locked_at, nil)
|
||||
end
|
||||
|
||||
def locked?(comparison_time)
|
||||
locked_at + LOCKING_PERIOD > comparison_time unless locked_at.nil?
|
||||
end
|
||||
|
||||
def to_param
|
||||
name.as_utf8
|
||||
def author_link(options = {})
|
||||
@web.make_link(author, nil, options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def continous_revision?(time, author)
|
||||
(current_revision.author == author) && (revised_at + 30.minutes > time)
|
||||
end
|
||||
def continous_revision?(created_at, author)
|
||||
@revisions.last.author == author && @revisions.last.created_at + 30.minutes > created_at
|
||||
end
|
||||
|
||||
# Forward method calls to the current revision, so the page responds to all revision calls
|
||||
def method_missing(method_symbol)
|
||||
revisions.last.send(method_symbol)
|
||||
end
|
||||
|
||||
# Forward method calls to the current revision, so the page responds to all revision calls
|
||||
def method_missing(method_id, *args, &block)
|
||||
method_name = method_id.to_s
|
||||
# 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
|
||||
|
|
23
app/models/page_lock.rb
Normal file
23
app/models/page_lock.rb
Normal 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
|
|
@ -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
|
|
@ -7,7 +7,7 @@ class PageSet < Array
|
|||
@web = web
|
||||
# if pages is not specified, make a list of all pages in the web
|
||||
if pages.nil?
|
||||
super(web.pages)
|
||||
super(web.pages.values)
|
||||
# otherwise use specified pages and condition to produce a set of pages
|
||||
elsif condition.nil?
|
||||
super(pages)
|
||||
|
@ -17,9 +17,10 @@ class PageSet < Array
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
|
||||
def by_name
|
||||
PageSet.new(@web, sort_by { |page| page.name })
|
||||
end
|
||||
|
@ -27,28 +28,22 @@ class PageSet < Array
|
|||
alias :sort :by_name
|
||||
|
||||
def by_revision
|
||||
PageSet.new(@web, sort_by { |page| page.revised_at }).reverse
|
||||
PageSet.new(@web, sort_by { |page| page.created_at }).reverse
|
||||
end
|
||||
|
||||
def pages_that_reference(page_name)
|
||||
all_referring_pages = WikiReference.pages_that_reference(@web, page_name)
|
||||
self.select { |page| all_referring_pages.include?(page.name) }
|
||||
self.select { |page| page.wiki_references.include?(page_name) }
|
||||
end
|
||||
|
||||
def pages_that_link_to(page_name)
|
||||
all_linking_pages = WikiReference.pages_that_link_to(@web, page_name)
|
||||
self.select { |page| all_linking_pages.include?(page.name) }
|
||||
self.select { |page| page.wiki_words.include?(page_name) }
|
||||
end
|
||||
|
||||
def pages_that_include(page_name)
|
||||
all_including_pages = WikiReference.pages_that_include(@web, page_name)
|
||||
self.select { |page| all_including_pages.include?(page.name) }
|
||||
self.select { |page| page.wiki_includes.include?(page_name) }
|
||||
end
|
||||
|
||||
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) }
|
||||
end
|
||||
|
||||
|
@ -62,43 +57,33 @@ class PageSet < Array
|
|||
# references and so cannot be orphans
|
||||
# Pages that refer to themselves and have no links from outside are oprphans.
|
||||
def orphaned_pages
|
||||
never_orphans = web.authors + ['HomePage']
|
||||
never_orphans = web.select.authors + ['HomePage']
|
||||
self.select { |page|
|
||||
if never_orphans.include? page.name
|
||||
false
|
||||
else
|
||||
references = (WikiReference.pages_that_reference(@web, page.name) +
|
||||
WikiReference.pages_redirected_to(@web, page.name)).uniq
|
||||
references.empty? or references == [page.name]
|
||||
references = pages_that_reference(page.name)
|
||||
references.empty? or references == [page]
|
||||
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
|
||||
# there are no pages in this page set's web
|
||||
def wanted_pages
|
||||
known_pages = (web.select.names + redirected_names).uniq
|
||||
wiki_words - known_pages
|
||||
wiki_words - web.select.names
|
||||
end
|
||||
|
||||
def names
|
||||
self.map { |page| page.name }
|
||||
end
|
||||
|
||||
def redirected_names
|
||||
self.wiki_words.select {|name| web.has_redirect_for?(name) }.uniq.sort
|
||||
def wiki_words
|
||||
self.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
|
||||
end
|
||||
|
||||
def wiki_words
|
||||
self.inject([]) { |wiki_words, page|
|
||||
wiki_words + page.wiki_words
|
||||
}.flatten.uniq.sort
|
||||
def authors
|
||||
self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,8 +1,127 @@
|
|||
class Revision < ActiveRecord::Base
|
||||
belongs_to :page
|
||||
composed_of :author, :mapping => [ %w(author name), %w(ip ip) ]
|
||||
require 'diff'
|
||||
require 'wiki_content'
|
||||
require 'chunks/wiki'
|
||||
require 'date'
|
||||
require 'author'
|
||||
require 'page'
|
||||
|
||||
def content
|
||||
read_attribute(:content).as_utf8
|
||||
class Revision
|
||||
|
||||
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
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
class System < ActiveRecord::Base
|
||||
set_table_name 'system'
|
||||
validates_presence_of :password
|
||||
end
|
|
@ -1,221 +1,133 @@
|
|||
require 'instiki_stringsupport'
|
||||
require 'cgi'
|
||||
require 'page'
|
||||
require 'page_set'
|
||||
require 'wiki_words'
|
||||
require 'zip/zip'
|
||||
|
||||
class Web < ActiveRecord::Base
|
||||
## Associations
|
||||
class Web
|
||||
attr_accessor :name, :password, :safe_mode, :pages
|
||||
attr_accessor :additional_style, :allow_uploads, :published
|
||||
attr_reader :address
|
||||
|
||||
has_many :pages, :dependent => :destroy
|
||||
has_many :wiki_files, :dependent => :destroy
|
||||
# there are getters for all these attributes, too
|
||||
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
|
||||
after_save :create_files_directory
|
||||
|
||||
before_validation :validate_address
|
||||
|
||||
## 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
|
||||
@pages = {}
|
||||
@allow_uploads = true
|
||||
@additional_style = nil
|
||||
@published = false
|
||||
@count_pages = false
|
||||
end
|
||||
|
||||
def settings_changed?(markup, safe_mode, brackets_only)
|
||||
self.markup != markup ||
|
||||
self.safe_mode != safe_mode ||
|
||||
self.brackets_only != brackets_only
|
||||
# Explicitly sets value of some web attributes to defaults, unless they are already set
|
||||
def set_compatible_defaults
|
||||
@markup = markup()
|
||||
@color = color()
|
||||
@safe_mode = safe_mode()
|
||||
@brackets_only = brackets_only()
|
||||
@max_upload_size = max_upload_size()
|
||||
@wiki = wiki
|
||||
end
|
||||
|
||||
def add_page(name, content, time, author, renderer)
|
||||
page = page(name) || pages.build(:name => name)
|
||||
page.revise(content, name, time, author, renderer)
|
||||
# All below getters know their default values. This is necessary to ensure compatibility with
|
||||
# 0.9 storages, where they were not defined.
|
||||
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
|
||||
|
||||
# @return [Array<String>] a collection of all the names of the authors that
|
||||
# have ever contributed to the pages for this Web
|
||||
def authors
|
||||
revisions.all(
|
||||
:select => "DISTINCT revisions.author",
|
||||
:order => "1"
|
||||
).collect(&:author)
|
||||
select.authors
|
||||
end
|
||||
|
||||
def categories
|
||||
select.map { |page| page.categories }.flatten.uniq.sort
|
||||
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)
|
||||
pages.exists?(:name => name)
|
||||
pages[name]
|
||||
end
|
||||
|
||||
def has_redirect_for?(name)
|
||||
WikiReference.page_that_redirects_for(self, name)
|
||||
def has_file?(name)
|
||||
wiki.file_yard(self).has_file?(name)
|
||||
end
|
||||
|
||||
def page_that_redirects_for(name)
|
||||
page(WikiReference.page_that_redirects_for(self, name))
|
||||
end
|
||||
|
||||
def has_file?(file_name)
|
||||
wiki_files.exists?(:file_name => file_name)
|
||||
end
|
||||
|
||||
def file_list(sort_order="file_name")
|
||||
wiki_files.all(:order => sort_order)
|
||||
end
|
||||
|
||||
def pages_that_link_to(page_name)
|
||||
WikiReference.pages_that_link_to(self, page_name)
|
||||
end
|
||||
|
||||
def pages_that_link_to_file(file_name)
|
||||
WikiReference.pages_that_link_to_file(self, file_name)
|
||||
end
|
||||
|
||||
# @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
|
||||
# Create a link for the given page name and link text based
|
||||
# on the render mode in options and whether the page exists
|
||||
# in the this web.
|
||||
# The links a relative, and will work only if displayed on another WikiPage.
|
||||
# It should not be used in menus, templates and such - instead, use link_to_page helper
|
||||
def make_link(name, text = nil, options = {})
|
||||
text = CGI.escapeHTML(text || WikiWords.separate(name))
|
||||
mode = options[:mode] || :show
|
||||
base_url = options[:base_url] || '..'
|
||||
link_type = options[:link_type] || :show
|
||||
case link_type.to_sym
|
||||
when :show
|
||||
UrlGenerator.new.make_page_link(mode, name, text, base_url, has_page?(name))
|
||||
when :file
|
||||
UrlGenerator.new.make_file_link(mode, name, text, base_url, has_file?(name))
|
||||
when :pic
|
||||
UrlGenerator.new.make_pic_link(mode, name, text, base_url, has_file?(name))
|
||||
else
|
||||
raise "Unknown link type: #{link_type}"
|
||||
end
|
||||
end
|
||||
|
||||
# OPTIMIZE Use the +delete_all+ with conditions for extra efficiency
|
||||
def remove_pages(pages_to_be_removed)
|
||||
pages_to_be_removed.each { |p| p.destroy }
|
||||
# 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 revised_at
|
||||
def refresh_revisions
|
||||
select.each { |page| page.revisions.each { |revision| revision.clear_display_cache } }
|
||||
end
|
||||
|
||||
def remove_pages(pages_to_be_removed)
|
||||
pages.delete_if { |page_name, page| pages_to_be_removed.include?(page) }
|
||||
end
|
||||
|
||||
def revised_on
|
||||
select.most_recent_revision
|
||||
end
|
||||
|
||||
def select(&condition)
|
||||
PageSet.new(self, pages, 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")
|
||||
PageSet.new(self, @pages.values, condition)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Returns an array of all the wiki words in any current revision
|
||||
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
|
||||
|
||||
# Returns an array of all the page names on this web
|
||||
def page_names
|
||||
pages.map { |p| p.name }
|
||||
pages.keys
|
||||
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
|
||||
|
|
|
@ -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
209
app/models/wiki_content.rb
Normal 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
|
|
@ -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
|
|
@ -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
259
app/models/wiki_service.rb
Normal 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
23
app/models/wiki_words.rb
Normal 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
|
|
@ -1,4 +1,4 @@
|
|||
<%- @title = "Instiki Setup"; @content_width = 500 -%>
|
||||
<% @title = "Instiki Setup"; @content_width = 500 %>
|
||||
|
||||
<p>
|
||||
Congratulations on succesfully installing and starting Instiki.
|
||||
|
@ -6,9 +6,8 @@
|
|||
you'll need to do a brief one-time setup.
|
||||
</p>
|
||||
|
||||
<% form_tag({ :controller => 'admin', :action => 'create_system' },
|
||||
{ 'id' => 'setup', 'method' => 'post', 'onsubmit' => 'return validateSetup()',
|
||||
'accept-charset' => 'utf-8' }) do
|
||||
<%= form_tag({ :controller => 'admin', :action => 'create_system'},
|
||||
{'id' => 'setup', 'method' => 'post', 'onSubmit' => 'return validateSetup()'})
|
||||
%>
|
||||
<ol class="setup">
|
||||
<li>
|
||||
|
@ -21,10 +20,10 @@
|
|||
The address can only consist of letters and digits.
|
||||
</div>
|
||||
<div class="inputBox">
|
||||
<label for="web_name">Name:</label> <input type="text" id="web_name" name="web_name" value="Wiki"
|
||||
onchange="proposeAddress();" onclick="this.value == 'Wiki' ? this.value = '' : true" />
|
||||
  
|
||||
<label for="web_address">Address:</label> <input type="text" id="web_address" name="web_address" onchange="cleanAddress();"
|
||||
Name: <input type="text" id="web_name" name="web_name" value="Wiki"
|
||||
onChange="proposeAddress();" onClick="this.value == 'Wiki' ? this.value = '' : true" />
|
||||
|
||||
Address: <input type="text" id="web_address" name="web_address" onChange="cleanAddress();"
|
||||
value="wiki" />
|
||||
</div>
|
||||
</li>
|
||||
|
@ -32,23 +31,23 @@
|
|||
<li>
|
||||
<h2 style="margin-bottom: 3px">Password for creating and changing webs</h2>
|
||||
<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 class="help"><em>Everyone with this password will be able to do this, so pick it carefully!</em></div>
|
||||
<div class="inputBox">
|
||||
<label for="password">Password:</label> <input type="password" id="password" name="password" />
|
||||
  
|
||||
<label for="password_check">Verify:</label> <input type="password" id="password_check" name="password_check" />
|
||||
Password: <input type="password" id="password" name="password" />
|
||||
|
||||
Verify: <input type="password" id="password_check" name="password_check" />
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<p style="text-align:right">
|
||||
<p align="right">
|
||||
<input type="submit" value="Setup" style="margin-left: 40px" />
|
||||
</p>
|
||||
<% end %>
|
||||
<%= end_form_tag %>
|
||||
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
function proposeAddress() {
|
||||
document.getElementById('web_address').value =
|
||||
document.getElementById('web_name').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
<%- @title = "New Wiki Web"; @content_width = 500 -%>
|
||||
<% @title = "New Wiki Web"; @content_width = 500 %>
|
||||
|
||||
<p>
|
||||
Each web serves as an isolated name space for wiki pages,
|
||||
so different subjects or projects can write about different <i>MuppetShows</i>.
|
||||
</p>
|
||||
|
||||
<% form_tag({ :controller => 'admin', :action => 'create_web' },
|
||||
{ 'id' => 'setup', 'method' => 'post',
|
||||
'onsubmit' => 'cleanAddress(); return validateSetup()',
|
||||
'accept-charset' => 'utf-8' }) do
|
||||
<%= form_tag({ :controller => 'admin', :action => 'create_web'},
|
||||
{'id' => 'setup', 'method' => 'post', 'onSubmit' => 'cleanAddress(); return validateSetup()'})
|
||||
%>
|
||||
|
||||
<ol class="setup">
|
||||
|
@ -21,24 +19,26 @@
|
|||
The address can only consist of letters and digits.
|
||||
</div>
|
||||
<div class="inputBox">
|
||||
<label for="web_name">Name:</label> <input type="text" id="web_name" name="name" onchange="proposeAddress();" />
|
||||
  
|
||||
<label for="web_address">Address:</label> <input type="text" id="web_address" name="address" onchange="cleanAddress();" />
|
||||
Name: <input type="text" id="web_name" name="name" onChange="proposeAddress();" />
|
||||
|
||||
Address: <input type="text" id="web_address" name="address" onChange="cleanAddress();" />
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
|
||||
<p style="text-align:right;font-size:.85em;">
|
||||
<label for="system_password">Enter system password</label>
|
||||
<p align="right">
|
||||
<small>
|
||||
Enter system password
|
||||
<input type="password" id="system_password" name="system_password" />
|
||||
and
|
||||
<input type="submit" value="Create Web" />
|
||||
</small>
|
||||
</p>
|
||||
|
||||
<%- end -%>
|
||||
<%= end_form_tag %>
|
||||
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
function proposeAddress() {
|
||||
document.getElementById('web_address').value =
|
||||
document.getElementById('web_name').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
<%- @title = "Edit Web" -%>
|
||||
<% @title = "Edit Web" %>
|
||||
|
||||
<% form_tag({ :controller => 'admin', :action => 'edit_web', :web => @web.address },
|
||||
{ 'id' => 'setup', 'method' => 'post',
|
||||
'onsubmit' => 'cleanAddress(); return validateSetup()',
|
||||
'accept-charset' => 'utf-8' }) do
|
||||
<%= form_tag({ :controller => 'admin', :action => 'edit_web', :web => @web.address },
|
||||
{'id' => 'setup', 'method' => 'post', 'onSubmit' => 'cleanAddress(); return validateSetup()'})
|
||||
%>
|
||||
|
||||
<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>.
|
||||
</div>
|
||||
|
||||
<div class="inputBox">
|
||||
<label for ="name">Name:</label> <input type="text" id="name" name="name" class="disableAutoComplete" value="<%= @web.name %>"
|
||||
onchange="proposeAddress();" />   
|
||||
<label for="address">Address:</label> <input type="text" class="disableAutoComplete" id="address" name="address" value="<%= @web.address %>"
|
||||
onchange="cleanAddress();" />
|
||||
<em>(Letters and digits only)</em>
|
||||
<div class="inputBox, disableAutoComplete">
|
||||
Name: <input type="text" id="name" name="name" value="<%= @web.name %>"
|
||||
onChange="proposeAddress();" />
|
||||
Address: <input type="text" id="address" name="address" value="<%= @web.address %>"
|
||||
onChange="cleanAddress();" />
|
||||
<i>(Letters and digits only)</i>
|
||||
</div>
|
||||
|
||||
<h2 style="margin-bottom: 3px">Specialize</h2>
|
||||
<div class="inputBox">
|
||||
<label for="markup">Markup:</label>
|
||||
<select id="markup" name="markup">
|
||||
<%= html_options({'Textile' => :textile, 'Markdown' => :markdown, 'Markdown+itex2MML' => :markdownMML, 'Markdown+blahtex/PNG' => :markdownPNG,
|
||||
'Mixed' => :mixed, 'RDoc' => :rdoc }, @web.markup) %>
|
||||
<div class="inputBox, disableAutoComplete">
|
||||
Markup:
|
||||
<select name="markup">
|
||||
<%= html_options({'Textile' => :textile, 'Markdown' => :markdown, 'Mixed' => :mixed,
|
||||
'RDoc' => :rdoc }, @web.markup) %>
|
||||
</select>
|
||||
|
||||
  
|
||||
|
||||
|
||||
<label for="color">Color:</label>
|
||||
<select id="color" name="color">
|
||||
<%= html_options({
|
||||
'Green' => '008B26',
|
||||
'Purple' => '504685',
|
||||
'Red' => 'DA0006',
|
||||
'Orange' => 'C50',
|
||||
'Grey' => '8BA2B0',
|
||||
'Blue' => '204A87',
|
||||
'Brown' => '8F5902',
|
||||
'Scarlet Red' => 'AA0006',
|
||||
'Plum' => '5C3566'
|
||||
}, @web.color) %>
|
||||
Color:
|
||||
<select name="color">
|
||||
<%= html_options({ 'Green' => '008B26', 'Purple' => '504685', 'Red' => 'DA0006',
|
||||
'Orange' => 'FA6F00', 'Grey' => '8BA2B0' }, @web.color) %>
|
||||
</select>
|
||||
<br/>
|
||||
<p>
|
||||
<input type="checkbox" class="disableAutoComplete" id="safe_mode" name="safe_mode" <%= raw 'checked="checked"' if @web.safe_mode? %> />
|
||||
<label for="safe_mode">Safe mode
|
||||
<em>- strip HTML tags and stylesheet options from the content of all pages</em></label>
|
||||
<small>
|
||||
<input type="checkbox" name="safe_mode" <%= 'checked="on"' if @web.safe_mode %> />
|
||||
Safe mode
|
||||
<em>- strip HTML tags and stylesheet options from the content of all pages</em>
|
||||
<br/>
|
||||
<input type="checkbox" class="disableAutoComplete" id="brackets_only" name="brackets_only" <%= raw 'checked="checked"' if @web.brackets_only? %> />
|
||||
<label for="brackets_only">Brackets only
|
||||
<em>- require all wiki words to be as [[wiki word]], WikiWord links won't be created</em></label>
|
||||
<input type="checkbox" name="brackets_only" <%= 'checked="on"' if @web.brackets_only %> />
|
||||
Brackets only
|
||||
<em>- require all wiki words to be as [[wiki word]], WikiWord links won't be created</em>
|
||||
<br/>
|
||||
<input type="checkbox" class="disableAutoComplete" id="count_pages" name="count_pages" <%= raw 'checked="checked"' if @web.count_pages? %> />
|
||||
<label for="count_pages">Count pages</label>
|
||||
<input type="checkbox" name="count_pages" <%= 'checked="on"' if @web.count_pages %> />
|
||||
Count pages
|
||||
<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
|
||||
<input type="text" class="disableAutoComplete" name="max_upload_size" value="<%= @web.max_upload_size %>"
|
||||
size="20" />
|
||||
<input type="text" name="max_upload_size" value="<%= @web.max_upload_size %>"
|
||||
width="20" />
|
||||
kbytes
|
||||
<em>-
|
||||
allow users to upload pictures and other files and include them on wiki pages
|
||||
</em>
|
||||
<br/>
|
||||
-->
|
||||
</small>
|
||||
</p>
|
||||
|
||||
<a href="#" onclick="toggleView('additionalStyle');return false;">
|
||||
<a href="#"
|
||||
onClick="document.getElementById('additionalStyle').style.display='block';return false;">
|
||||
Stylesheet tweaks >></a>
|
||||
<em>
|
||||
<small><em>
|
||||
- add or change styles used by this web; styles defined here take precedence over
|
||||
instiki.css.<br/>
|
||||
Hint: View HTML source of a page you want to style to find ID names on individual
|
||||
tags.</em>
|
||||
instiki.css. Hint: View HTML source of a page you want to style to find ID names on individual
|
||||
tags.</em></small>
|
||||
<br/>
|
||||
<textarea id="additionalStyle" class="disableAutoComplete" cols="50" rows="20"
|
||||
style="display:none" name="additional_style"><%= h(@web.additional_style) %>
|
||||
<textarea id="additionalStyle"
|
||||
style="display: none; margin-top: 10px; margin-bottom: 5px; width: 560px; height: 200px"
|
||||
name="additional_style"><%= @web.additional_style %>
|
||||
</textarea>
|
||||
</div>
|
||||
|
||||
|
@ -86,10 +85,10 @@
|
|||
Setting the password to nothing will remove the password protection.
|
||||
</div>
|
||||
<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 %>" />
|
||||
  
|
||||
<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" />
|
||||
</div>
|
||||
|
||||
|
@ -97,77 +96,43 @@
|
|||
<div class="help">
|
||||
You can turn on a read-only version of this web that's accessible even when the regular web
|
||||
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 class="inputBox">
|
||||
<input type="checkbox" id="published" name="published" class="disableAutoComplete" <%= raw 'checked="checked"' if @web.published? %> />
|
||||
<label for="published">Publish this web</label>
|
||||
<input type="checkbox" name="published" <%= 'checked="on"' if @web.published %> />
|
||||
Publish this web
|
||||
</div>
|
||||
|
||||
<p style="text-align:right;font-size:.85em;">
|
||||
<label for="system_password">Enter system password</label>
|
||||
<p align="right">
|
||||
<small>
|
||||
Enter system password
|
||||
<input type="password" class="disableAutoComplete" id="system_password"
|
||||
name="system_password" />
|
||||
and
|
||||
<input type="submit" value="Update Web" />
|
||||
<br/><br/>
|
||||
...or forget changes and <%= link_to 'create a new web', :action => 'create_web' %>
|
||||
</small>
|
||||
</p>
|
||||
<%- end %>
|
||||
|
||||
<%= end_form_tag %>
|
||||
|
||||
<br/>
|
||||
<h1>Other administrative tasks</h1>
|
||||
|
||||
<% form_tag({:controller => 'admin', :web => @web.address, :action => 'remove_orphaned_pages'},
|
||||
{ :id => 'remove_orphaned_pages',
|
||||
:onsubmit => "return checkSystemPassword(document.getElementById('system_password_orphaned').value)",
|
||||
'accept-charset' => 'utf-8' }) do
|
||||
<%= form_tag({:controller => 'admin', :web => @web.address, :action => 'remove_orphaned_pages'},
|
||||
{:id => 'remove_orphaned_pages',
|
||||
:onSubmit => "return checkSystemPassword(document.getElementById('system_password_orphaned').value)"
|
||||
})
|
||||
%>
|
||||
<div class="inputBox">
|
||||
<%= link_to ' Manage uploaded files',
|
||||
{:controller => 'wiki', :web => @web.address, :action => 'file_list'}, :style => 'font-weight:bold' %>
|
||||
for this web (<%= @web.name %>)
|
||||
</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" />
|
||||
<p align="right">
|
||||
<small>
|
||||
Clean up by entering system password
|
||||
<input type="password" id="system_password_orphaned" name="system_password_orphaned" />
|
||||
and
|
||||
<input type="submit" value="Delete Orphan Pages" />
|
||||
</small>
|
||||
</p>
|
||||
<%- end -%>
|
||||
|
||||
<%- 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>
|
||||
<%= end_form_tag %>
|
||||
|
||||
<%= javascript_include_tag 'edit_web' %>
|
||||
|
|
|
@ -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 -%>
|
|
@ -1,33 +1,19 @@
|
|||
<%-
|
||||
@title = "Upload #{h @file_name}"
|
||||
<%
|
||||
@title = "Upload #{@file_name}"
|
||||
@hide_navigation = false
|
||||
@referring_page ||= request.env["HTTP_REFERER"]
|
||||
-%>
|
||||
%>
|
||||
|
||||
<%= error_messages_for 'file' %>
|
||||
|
||||
<% form_tag({ :controller => 'file', :web => @web_name, :action => 'file' },
|
||||
{ 'multipart' => true , 'accept-charset' => 'utf-8' }) do %>
|
||||
<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 />
|
||||
<input type="file" id="file_content" name="file[content]" size="40" />
|
||||
<br />
|
||||
Please note that the file you are uploading will be named <%= h @file_name %> on the wiki -
|
||||
regardless of how it is named on your computer. To change the wiki name of the file, please go
|
||||
<%= link_to("back", @referring_page) %> and edit the wiki page that refers to the file.
|
||||
</div>
|
||||
<div class="inputFieldWithPrompt">
|
||||
<label for="file_description"><b>Description</b>:</label>
|
||||
<p>
|
||||
<%= form_tag({:controller => 'file', :web => @web_name, :action => 'file'}, {:multipart => true}) %>
|
||||
<p>
|
||||
File to upload:
|
||||
<br/>
|
||||
<%= 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 -%>
|
||||
<input type="file" name="file" size="40" />
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" value="Update" /> as
|
||||
<input type="text" name="author" id="authorName" value="<%= @author %>"
|
||||
onClick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
|
||||
</p>
|
||||
<%= end_form_tag %>
|
||||
</p>
|
|
@ -1,5 +1,5 @@
|
|||
<p>
|
||||
<% form_tag({}, { 'multipart' => true, 'accept-charset' => 'utf-8' }) do %>
|
||||
<%= form_tag({}, {:multipart => true}) %>
|
||||
<p>
|
||||
File to upload:
|
||||
<br/>
|
||||
|
@ -13,11 +13,11 @@
|
|||
<p>
|
||||
<input type="submit" value="Update" /> as
|
||||
<input type="text" name="author" id="authorName" value="<%= @author %>"
|
||||
onclick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
|
||||
<%- if @page -%>
|
||||
| <%= link_to 'Cancel', :web => @web.address, :action => 'file'%> <em>(unlocks page)</em>
|
||||
<%- end -%>
|
||||
onClick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
|
||||
<% if @page %>
|
||||
| <%= link_to 'Cancel', :web => @web.address, :action => 'file'%> <small>(unlocks page)</small>
|
||||
<% end %>
|
||||
|
||||
</p>
|
||||
<%- end -%>
|
||||
<%= end_form_tag %>
|
||||
</p>
|
|
@ -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">
|
||||
<head>
|
||||
<title>
|
||||
<%- if @page and (@page.name == 'HomePage') and (%w( show published print ).include?(params['action'])) -%>
|
||||
<%= h(@web.name) + (@show_diff ? ' (changes)' : '') %>
|
||||
<%- elsif @web -%>
|
||||
<%= @title %> in <%= h @web.name %>
|
||||
<%- else -%>
|
||||
<% if @page and (@page.name == 'HomePage') and (%w( show published print ).include?(@action_name)) %>
|
||||
<%= @web.name %>
|
||||
<% elsif @web %>
|
||||
<%= @title %> in <%= @web.name %>
|
||||
<% else %>
|
||||
<%= @title %>
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
</title>
|
||||
|
||||
<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">
|
||||
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" %>;
|
||||
}
|
||||
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 %>
|
||||
<%= Rails.root.join('public', 'stylesheets', 'syntax.css').read if @inline_style %>
|
||||
<%= File.read(RAILS_ROOT + '/public/stylesheets/instiki.css') if @inline_style %>
|
||||
</style>
|
||||
<%= "<style type='text/css'>#{@style_additions}</style>".html_safe if @style_additions %>
|
||||
<style type="text/css"><!--/*--><![CDATA[/*><!--*/
|
||||
<%= @web && @web.additional_style ? @web.additional_style.html_safe : '' %>
|
||||
/*]]>*/--></style>
|
||||
<%= javascript_include_tag :defaults %>
|
||||
<%= csrf_meta_tag %>
|
||||
<%- if @web -%>
|
||||
<%- 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 -%>
|
||||
<%= 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 -%>
|
||||
|
||||
<%= stylesheet_link_tag 'instiki' unless @inline_style %>
|
||||
|
||||
<style type="text/css">
|
||||
<%= @style_additions %>
|
||||
<%= @web ? @web.additional_style : '' %>
|
||||
</style>
|
||||
|
||||
<% if @web %>
|
||||
<%= auto_discovery_link_tag(:rss, :controller => 'wiki', :web => @web.address, :action => 'rss_with_headlines') %>
|
||||
<%= auto_discovery_link_tag(:rss, :controller => 'wiki', :web => @web.address, :action => 'rss_with_content') %>
|
||||
<% end %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -64,35 +44,34 @@
|
|||
<div id="Container">
|
||||
<div id="Content">
|
||||
<h1 id="pageName">
|
||||
<%= render(:file => 'svg_logo') if xhtml_enabled? %>
|
||||
<%- if @page and (@page.name == 'HomePage') and %w( show published print ).include?(@action_name) -%>
|
||||
<%= h(@web.name) + (@show_diff ? ' (changes)' : '') %>
|
||||
<%- elsif @web -%>
|
||||
<span class="webName"><%= @web.name %></span><br />
|
||||
<% if @page and (@page.name == 'HomePage') and %w( show published print ).include?(@action_name) %>
|
||||
<%= @web.name %>
|
||||
<% elsif @web %>
|
||||
<small><%= @web.name %></small><br />
|
||||
<%= @title %>
|
||||
<%- else -%>
|
||||
<% else %>
|
||||
<%= @title %>
|
||||
<%- end %>
|
||||
<% end %>
|
||||
</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] -%>
|
||||
<div class="info"><%= escape_preserving_linefeeds flash[:info] %></div>
|
||||
<%- end -%>
|
||||
|
||||
<%- if @error or flash[:error] -%>
|
||||
<div class="errorExplanation"><%= escape_preserving_linefeeds(@error || flash[:error]) %></div>
|
||||
<%- end -%>
|
||||
<% if @flash[:info] %> <div id="info">
|
||||
<hr/><p><%= escape_preserving_linefeeds @flash[:info] %></p><hr/></div>
|
||||
<% end %>
|
||||
|
||||
<%= render 'navigation' unless @web.nil? || @hide_navigation %>
|
||||
<%= @content_for_layout %>
|
||||
|
||||
<%- if @show_footer -%>
|
||||
<% if @show_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>
|
||||
<div>Powered by <a href="http://rubyonrails.com/">Ruby on Rails</a> <%= "#{Rails::VERSION::STRING}" %></div>
|
||||
<p>This site is running on <a href="http://instiki.org/">Instiki</a></p>
|
||||
<br/>
|
||||
<p>Powered by <a href="http://rubyonrails.com/">Ruby on Rails</a></p>
|
||||
</div>
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
|
||||
</div> <!-- Content -->
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -1 +0,0 @@
|
|||
<%= @content_for_layout %>
|
|
@ -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}
|
|
@ -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") -%>
|
|
@ -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") -%>
|
|
@ -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>
|
||||
<%= render(:file => "markdown_table") -%>
|
||||
<h3>Markdown formatting tips (<a target="_new" href="http://daringfireball.net/projects/markdown/syntax">advanced</a>)</h3>
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr><td>_your text_</td><td class="arrow">→</td><td><em>your text</em></td></tr>
|
||||
<tr><td>**your text**</td><td class="arrow">→</td><td><strong>your text</strong></td></tr>
|
||||
<tr><td>`my code`</td><td class="arrow">→</td><td><code>my code</code></td></tr>
|
||||
<tr><td>* Bulleted list<br />* Second item</td><td class="arrow">→</td><td>• Bulleted list<br />• Second item</td></tr>
|
||||
<tr><td>1. Numbered list<br />1. Second item</td><td class="arrow">→</td><td>1. Numbered list<br />2. Second item</td></tr>
|
||||
<tr><td>[link name](URL)</td><td class="arrow">→</td><td><a href="URL">link name</a></td></tr>
|
||||
<tr><td>***</td><td class="arrow">→</td><td>Horizontal ruler</td></tr>
|
||||
<tr><td><http://url><br /><email@add.com></td><td class="arrow">→</td><td>Auto-linked</td></tr>
|
||||
<tr><td>![Alt text](URL)</td><td class="arrow">→</td><td>Image</td></tr>
|
||||
</table>
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
<table cellspacing="0" cellpadding="0">
|
||||
<tr><td>_your text_</td><td class="arrow">→</td><td><em>your text</em></td></tr>
|
||||
<tr><td>**your text**</td><td class="arrow">→</td><td><strong>your text</strong></td></tr>
|
||||
<tr><td>`my code`</td><td class="arrow">→</td><td><code>my code</code></td></tr>
|
||||
<tr><td>* Bulleted list<br />* Second item</td><td class="arrow">→</td><td>• Bulleted list<br />• Second item</td></tr>
|
||||
<tr><td>1. Numbered list<br />1. Second item</td><td class="arrow">→</td><td>1. Numbered list<br />2. Second item</td></tr>
|
||||
<tr><td>Definition list<br />: is useful</td><td class="arrow">→</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">→</td><td><a href="URL">link name</a></td></tr>
|
||||
<tr><td>![Alt text](URL)</td><td class="arrow">→</td><td>Image</td></tr>
|
||||
<tr><td>## Header ##<br />### Subheader ###<br />#### Subsubhead####<br/>##### Etc. #####</td><td class="arrow">→</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">→</td><td>Horizontal rule</td></tr>
|
||||
<tr><td>Some text[^fine] here.<br/>[^fine]: The fine print.</td><td class="arrow">→</td><td>A footnote</td></tr>
|
||||
<tr><td>*[LA]: Los Angeles<br/>*[ppm]: parts per million</td><td class="arrow">→</td><td>Abbreviations</td></tr>
|
||||
</table>
|
|
@ -1,4 +1,4 @@
|
|||
<%= render(:file => 'textile_help') %>
|
||||
<%= render 'textile_help' %>
|
||||
|
||||
<h3>Markdown</h3>
|
||||
<p>
|
||||
|
|
|
@ -8,24 +8,25 @@ def list_item(text, link_options, description, accesskey = nil)
|
|||
end
|
||||
%>
|
||||
|
||||
<div class="navigation">
|
||||
<span class="skipNav"><a href='#navEnd'>Skip the Navigation Links</a> | </span>
|
||||
<%- if params['action'] != 'published' then -%>
|
||||
<%= form_tag({ :controller => 'wiki', :action => 'search', :web => @web.address},
|
||||
{'id' => 'navigationForm', 'class' => 'navigation', 'method' => 'get'})
|
||||
%>
|
||||
|
||||
<% if @action_name != 'published' then %>
|
||||
<%= 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 '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 'Feeds', {:action => 'feeds'}, 'Subscribe to changes by Atom' %> |
|
||||
<%= list_item 'Export', {:action => 'export'}, 'Download a zip with all the pages in this wiki', 'X' %> |
|
||||
<% form_tag({ :controller => 'wiki', :action => 'search', :web => @web.address},
|
||||
{'id' => 'navigationSearchForm', 'method' => 'get', 'accept-charset' => 'utf-8' }) do %>
|
||||
<fieldset class="search"><input type="text" id="searchField" name="query" value="Search"
|
||||
onfocus="this.value == 'Search' ? this.value = '' : true"
|
||||
onblur="this.value == '' ? this.value = 'Search' : true" /></fieldset>
|
||||
<% end %>
|
||||
<%- else -%>
|
||||
<%= list_item 'Home Page', {:action => 'published', :id => 'HomePage'}, 'Home, Sweet Home', 'H' %> |
|
||||
<%= list_item 'Feeds', {:action => 'feeds'}, 'Subscribe to changes by Atom' %>
|
||||
<% end-%>
|
||||
<span id='navEnd'></span>
|
||||
</div>
|
||||
<%= 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'
|
||||
%> |
|
||||
<input type="text" id="searchField" name="query" style="font-size: 10px" value="Search"
|
||||
onfocus="if (this.value == 'Search' ) this.value = '' " />
|
||||
<% else %>
|
||||
<%= list_item 'Home Page', {:action => 'published', :id => 'HomePage'}, 'Home, Sweet Home', 'H' %> |
|
||||
<% end%>
|
||||
|
||||
<%= end_form_tag %>
|
||||
|
|
|
@ -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>
|
|
@ -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">
|
||||
<tr><td>_your text_</td><td class="arrow">→</td><td><em>your text</em></td></tr>
|
||||
<tr><td>*your text*</td><td class="arrow">→</td><td><strong>your text</strong></td></tr>
|
||||
<tr><td style="padding-right:0.75em">%{color:red}hello%</td><td class="arrow">→</td><td><span style="color: red;">hello</span></td></tr>
|
||||
<tr><td>%{color:red}hello%</td><td class="arrow">→</td><td><span style="color: red;">hello</span></td></tr>
|
||||
<tr><td>* Bulleted list<br />* Second item</td><td class="arrow">→</td><td>• Bulleted list<br />• Second item</td></tr>
|
||||
<tr><td># Numbered list<br /># Second item</td><td class="arrow">→</td><td>1. Numbered list<br />2. Second item</td></tr>
|
||||
<tr><td>"linkname":URL</td><td class="arrow">→</td><td><a href="URL">linkname</a></td></tr>
|
||||
<tr><td>|a|table|row|<br />|b|table|row|</td><td class="arrow">→</td><td>Table</td></tr>
|
||||
<tr><td>http://url<br />email@address.com</td><td class="arrow">→</td><td>Auto-linked</td></tr>
|
||||
<tr><td>!imageURL!</td><td class="arrow">→</td><td>Image</td></tr>
|
||||
</table>
|
||||
|
||||
<script language="JavaScript">
|
||||
function quickRedReference() {
|
||||
window.open(
|
||||
"http://redcloth.org/hobix.com/textile/quick.html",
|
||||
"http://hobix.com/textile/quick.html",
|
||||
"redRef",
|
||||
"height=600,width=550,channelmode=0,dependent=0," +
|
||||
"directories=0,fullscreen=0,location=0,menubar=0," +
|
||||
|
|
|
@ -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 -%>
|
|
@ -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
|
|
@ -1,11 +1,11 @@
|
|||
<%- @title = 'Authors' -%>
|
||||
<% @title = 'Authors' %>
|
||||
|
||||
<ul id="authorList">
|
||||
<%- for author in @authors -%>
|
||||
<% for author in @authors %>
|
||||
<li>
|
||||
<%= link_to_page author.purify %>
|
||||
<%= link_to_page author %>
|
||||
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>
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
|
|
@ -1,79 +1,37 @@
|
|||
<%-
|
||||
@title = "Editing #{@page.name.escapeHTML}".html_safe
|
||||
<%
|
||||
@title = "Editing #{@page.name}"
|
||||
@content_width = 720
|
||||
@hide_navigation = true
|
||||
-%>
|
||||
%>
|
||||
|
||||
<div id="MarkupHelp">
|
||||
<%= render(:file => "#{@web.markup}_help") -%>
|
||||
<%= render(:file => 'wiki_words_help') unless @web.brackets_only? -%>
|
||||
<div id="MarkupHelp" style="float: right; width: 250px; margin-top: 5px">
|
||||
<%= render("#{@web.markup}_help") %>
|
||||
<%= render 'wiki_words_help' %>
|
||||
</div>
|
||||
|
||||
<% form_tag({ :action => 'save', :web => @web.address, :id => @page.name },
|
||||
{ '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>
|
||||
<%= check_box_tag :alter_title, value = "1", checked=false,
|
||||
'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>
|
||||
<% else -%>
|
||||
<%= hidden_field_tag 'new_name', @page_name %>
|
||||
<% end%>
|
||||
<div id="editFormButtons">
|
||||
<input type="submit" value="Submit" accesskey="s"/> as
|
||||
<%= text_field_tag :author, h(@author.purify),
|
||||
:onfocus => "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},
|
||||
{:accesskey => 'c'}) %>
|
||||
<span class="unlock">(unlocks page)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<%- end -%>
|
||||
<%= form_tag({ :action => 'save', :web => @web.address, :id => @page.name},
|
||||
{'id' => 'editForm', 'method' => 'post', 'onSubmit' => 'cleanAuthorName()'})
|
||||
%>
|
||||
|
||||
<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
|
||||
}
|
||||
}
|
||||
<p>
|
||||
<textarea name="content" style="width: 450px; height: 500px"><%= h(@flash[:content] || @page.content) %></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" value="Submit" accesskey="s"/> as
|
||||
<input type="text" name="author" id="authorName" value="<%= @author %>"
|
||||
onClick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
|
||||
|
|
||||
<%= link_to('Cancel', {:web => @web.address, :action => 'cancel_edit', :id => @page.name},
|
||||
{:accesskey => 'c'})
|
||||
%>
|
||||
<small>(unlocks page)</small>
|
||||
</p>
|
||||
<%= end_form_tag %>
|
||||
|
||||
<script language="JavaScript1.2">
|
||||
function cleanAuthorName() {
|
||||
if (document.getElementById('authorName').value == "") {
|
||||
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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
<ul id="feedsList">
|
||||
<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>
|
||||
<% 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>
|
||||
|
|
|
@ -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">
|
||||
<%- if @rss_with_content_allowed -%>
|
||||
<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>
|
||||
<%- end -%>
|
||||
<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>
|
||||
</ul>
|
||||
|
|
|
@ -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 -%>
|
|
@ -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 -%>
|
|
@ -1,90 +1,64 @@
|
|||
<%- @title = "All Pages" -%>
|
||||
<% @title = "All Pages" %>
|
||||
|
||||
<%= categories_menu unless @categories.empty? %>
|
||||
|
||||
<div id="allPages">
|
||||
<%- unless @pages_that_are_orphaned.empty? && @page_names_that_are_wanted.empty? -%>
|
||||
<div id="allPages" style="float: left; width: 280px; margin-right: 30px">
|
||||
<% unless @pages_that_are_orphaned.empty? && @page_names_that_are_wanted.empty? %>
|
||||
<h2>
|
||||
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>
|
||||
<%- end -%>
|
||||
<% 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 %>
|
||||
</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>
|
||||
<%- @pages_in_category.each do |page| -%>
|
||||
<% @pages_by_name.each do |page| %>
|
||||
<li>
|
||||
<%= link_to_existing_page page, truncate(page.plain_name, :length => 35) %>
|
||||
<%= link_to_existing_page page, truncate(page.plain_name, 35) %>
|
||||
</li>
|
||||
<%- end -%>
|
||||
</ul>
|
||||
<%- end -%>
|
||||
<% end %></ul>
|
||||
|
||||
<%- if @web.count_pages? -%>
|
||||
<% if @web.count_pages %>
|
||||
<% total_chars = @pages_in_category.characters %>
|
||||
<p class="pageStats">All content: <%= total_chars %> chars / approx. <%= sprintf("%-.1f", (total_chars / 2275 )) %> printed pages</p>
|
||||
<%- end -%>
|
||||
<p><small>All content: <%= total_chars %> chars / <%= sprintf("%-.1f", (total_chars / 2275 )) %> pages</small></p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div id="wantedPages">
|
||||
<%- unless @page_names_that_are_wanted.empty? -%>
|
||||
<div style="float: left; width: 280px">
|
||||
<% unless @page_names_that_are_wanted.empty? %>
|
||||
<h2>
|
||||
Wanted Pages
|
||||
<br/>
|
||||
<span class="pageType">
|
||||
Nonexistent pages that other pages in <%= raw @set_name %> reference
|
||||
</span>
|
||||
<small style="font-size: 12px">
|
||||
<i>Unexisting pages that other pages in <%= @set_name %> reference</i>
|
||||
</small>
|
||||
</h2>
|
||||
|
||||
<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>
|
||||
<%= 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
|
||||
<%= @web.select.pages_that_reference(wanted_page_name).collect { |referring_page|
|
||||
link_to_existing_page referring_page
|
||||
}.join(", ").html_safe
|
||||
}.join(", ")
|
||||
%>
|
||||
</li>
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
</ul>
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
|
||||
<%- unless @pages_that_are_orphaned.empty? -%>
|
||||
<% unless @pages_that_are_orphaned.empty? %>
|
||||
<h2>
|
||||
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>
|
||||
|
||||
<ul style="margin-bottom: 35px">
|
||||
<%- @pages_that_are_orphaned.each do |orphan_page| -%>
|
||||
<% @pages_that_are_orphaned.each do |orphan_page| %>
|
||||
<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>
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
</ul>
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<%- @title = "#{@page.plain_name} is locked".html_safe -%>
|
||||
<% @title = "#{@page.plain_name} is locked" %>
|
||||
|
||||
<p>
|
||||
<%= link_to_page(h(@page.locked_by.purify)) %>
|
||||
<%- if @page.lock_duration(Time.now) == 0 -%>
|
||||
<%= link_to_page(@page.locked_by) %>
|
||||
<% if @page.lock_duration(Time.now) == 0 %>
|
||||
just started editing this page.
|
||||
<%- else -%>
|
||||
<% else %>
|
||||
has been editing this page for <%= @page.lock_duration(Time.now) %> minutes.
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
|
|
@ -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>
|
||||
This web is password-protected. Please enter the password.
|
||||
<% if @web.published? %>
|
||||
If you don't have the password, you can view this wiki as a <%= link_to 'read-only version', :action => 'published', :id => 'HomePage' %>.
|
||||
<% end %>
|
||||
</p>
|
||||
<p>
|
||||
<label for="password"><b>Password:</b></label>
|
||||
<input type="password" name="password" id="password" />
|
||||
<input type="submit" value="Login" />
|
||||
</p>
|
||||
<%- end -%>
|
||||
<p>
|
||||
<%= form_tag({ :controller => 'wiki', :action => 'authenticate', :web => @web.address},
|
||||
{ 'name' => 'loginForm', 'id' => 'loginForm', 'method' => 'post'})
|
||||
%>
|
||||
<b>Password</b><br />
|
||||
<input type="password" name="password" id="password" default="yes" />
|
||||
<%= end_form_tag %>
|
||||
</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
document.forms["loginForm"].elements["password"].focus();
|
||||
<script language="JavaScript">
|
||||
document.forms["loginForm"].elements["password"].focus();
|
||||
</script>
|
||||
|
|
|
@ -1,38 +1,31 @@
|
|||
<%-
|
||||
@title = "Creating #{WikiWords.separate(@page_name).escapeHTML}".html_safe
|
||||
<%
|
||||
@title = "Creating #{WikiWords.separate(@page_name)}"
|
||||
@content_width = 720
|
||||
@hide_navigation = true
|
||||
-%>
|
||||
%>
|
||||
|
||||
<div id="MarkupHelp">
|
||||
<%= render(:file => "#{@web.markup}_help") -%>
|
||||
<%= render(:file => 'wiki_words_help') unless @web.brackets_only? -%>
|
||||
<div id="MarkupHelp" style="float: right; width: 250px; margin-top: 5px">
|
||||
<%= render("#{@web.markup}_help") %>
|
||||
<%= render 'wiki_words_help' %>
|
||||
</div>
|
||||
|
||||
<div id="editForm">
|
||||
<% form_tag({ :action => 'save', :web => @web.address, :id => @page_name },
|
||||
{ 'id' => 'editForm', 'method' => 'post', 'onsubmit' => 'cleanAuthorName();', 'accept-charset' => 'utf-8' }) do %>
|
||||
<%= form_tag({ :action => 'save', :web => @web.address, :id => @page_name},
|
||||
{'id' => 'editForm', 'method' => 'post', 'onSubmit' => 'cleanAuthorName();'})
|
||||
%>
|
||||
|
||||
<textarea name="content" id="content" rows="24" cols="60"><%= h(flash[:content] ||
|
||||
params['content'] ? params['content'].purify : '' ) %></textarea>
|
||||
<div id="editFormButtons">
|
||||
<input type="submit" value="Submit" accesskey="s"/> as
|
||||
<%= text_field_tag :author, @author,
|
||||
:onfocus => "this.value == 'AnonymousCoward' ? this.value = '' : true;",
|
||||
:onblur => "this.value == '' ? this.value = 'AnonymousCoward' : true" %>
|
||||
</div>
|
||||
<%- end -%>
|
||||
</div>
|
||||
<p>
|
||||
<textarea name="content" style="width: 450px; height: 500px"><%= h(@flash[:content] || '') %></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" value="Submit" accesskey="s"/> as
|
||||
<input type="text" name="author" id="authorName" value="<%= @author %>" onClick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
|
||||
</p>
|
||||
<%= end_form_tag %>
|
||||
|
||||
<script type="text/javascript">
|
||||
<script language="JavaScript1.2">
|
||||
function cleanAuthorName() {
|
||||
if (document.getElementById('authorName').value == "") {
|
||||
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>
|
||||
|
|
|
@ -1,54 +1,120 @@
|
|||
<%-
|
||||
<%
|
||||
@title = @page.plain_name
|
||||
@title += ' (changes)' if @show_diff
|
||||
@show_footer = true
|
||||
-%>
|
||||
%>
|
||||
|
||||
<div id="revision">
|
||||
<%- if @show_diff -%>
|
||||
<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 -%>
|
||||
<%= @page.display_content %>
|
||||
</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">
|
||||
<%= @page.revisions? ? "Revised" : "Created" %> on <%= format_date(@page.revised_at) %>
|
||||
by <%= author_link(@page) %>
|
||||
<%= @page.revisions? ? "Revised" : "Created" %> on <%= @page.pretty_created_at %>
|
||||
by <%= @page.author_link %>
|
||||
<%= "(#{@page.author.ip})" if @page.author.respond_to?(:ip) %>
|
||||
<% if @web.count_pages? %>
|
||||
<% if @web.count_pages %>
|
||||
<% total_chars = @page.content.length %>
|
||||
(<%= total_chars %> characters / <%= sprintf("%-.1f", (total_chars / 2275 rescue 0)) %> pages)
|
||||
<% end %>
|
||||
</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:
|
||||
<%= link_to('Print',
|
||||
{ :web => @web.address, :action => 'print', :id => @page.name },
|
||||
{ :accesskey => 'p', :id => 'view_print', :rel => 'nofollow' }) %>
|
||||
<%- if @web.markup == :markdownMML or @web.markup == :markdown or @web.markup == :markdownPNG -%>
|
||||
{:web => @web.address, :action => 'print', :id => @page.name},
|
||||
{:accesskey => 'p', :name => 'view_print'}) %>
|
||||
<% if defined? RedClothForTex and RedClothForTex.available? and @web.markup == :textile %>
|
||||
|
|
||||
<%= link_to 'TeX', {:web => @web.address, :action => 'tex', :id => @page.name},
|
||||
{:id => 'view_tex', :rel => 'nofollow' } %>
|
||||
<% if WikiReference.pages_in_category(@web, 'S5-slideshow').map.include?(@page.name) %>
|
||||
{:name => 'view_tex'} %>
|
||||
|
|
||||
<%= link_to 'S5', {:web => @web.address, :action => 's5', :id => @page.name},
|
||||
{:id => 'view_S5'} %>
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
|
|
||||
<%= link_to 'Source', {:web => @web.address, :action => 'source', :id => @page.name},
|
||||
{:id => 'view_source', :rel => 'nofollow' } %>
|
||||
</span>
|
||||
<%= link_to 'PDF', {:web => @web.address, :action => 'pdf', :id => @page.name},
|
||||
{:name => 'view_pdf'} %>
|
||||
<% end %>
|
||||
</small>
|
||||
|
||||
<%= 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>
|
||||
|
||||
<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>
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<%-
|
||||
<%
|
||||
@title = @page.plain_name
|
||||
@hide_navigation = true
|
||||
@style_additions = ".newWikiWord { background-color: white; font-style: italic; }"
|
||||
-%>
|
||||
@inline_style = true
|
||||
%>
|
||||
|
||||
<%= @renderer.display_content %>
|
||||
<%= @page.display_content_for_export %>
|
||||
|
||||
<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, { :mode => (@link_mode || :show) }) %>
|
||||
<%= @page.author_link({ :mode => (@link_mode || :show) }) %>
|
||||
</div>
|
||||
|
|
|
@ -1,25 +1,9 @@
|
|||
<%-
|
||||
<%
|
||||
@title = @page.plain_name
|
||||
@hide_navigation = false
|
||||
@style_additions = ".newWikiWord { background-color: white; font-style: italic; }"
|
||||
@inline_style = false
|
||||
@inline_style = true
|
||||
@show_footer = true
|
||||
-%>
|
||||
%>
|
||||
|
||||
<%= @renderer.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>
|
||||
<%= @page.display_published %>
|
||||
|
|
|
@ -1,25 +1,27 @@
|
|||
<%- @title = "Recently Revised" -%>
|
||||
<% @title = "Recently Revised" %>
|
||||
|
||||
<%= categories_menu %>
|
||||
|
||||
<%- @pages_by_day.keys.sort.reverse.each do |day| -%>
|
||||
<h3><%= format_date(day, include_time = false) %></h3>
|
||||
<% unless @pages_by_revision.empty? %>
|
||||
<% revision_date = @pages_by_revision.first.revised_on %>
|
||||
<h3><%= revision_date.strftime('%B %e, %Y') %></h3>
|
||||
<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>
|
||||
<%= 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">
|
||||
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) %>
|
||||
</div>
|
||||
</li>
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
</ul>
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
|
|
@ -1,31 +1,103 @@
|
|||
<%-
|
||||
@title = "#{@page.plain_name} (Rev ##{@revision_number}#{@show_diff ? ', changes' : ''})".html_safe
|
||||
-%>
|
||||
|
||||
<% @title = "#{@page.plain_name} (Rev ##{@revision.number})" %>
|
||||
|
||||
<div id="revision">
|
||||
<%- if @show_diff -%>
|
||||
<p class="show_diff">
|
||||
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>
|
||||
</p>
|
||||
<%= @renderer.display_diff %>
|
||||
<%- else -%>
|
||||
<%= @renderer.display_content %>
|
||||
<%- end -%>
|
||||
</div> <!-- Revision -->
|
||||
<%= @revision.display_content %>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<%= @revision.display_diff %>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="byline">
|
||||
<%= "Revision from #{format_date(@revision.revised_at)} by" %>
|
||||
<%= link_to_page @revision.author.purify %>
|
||||
<%= "Revision from #{@revision.pretty_created_at} by" %>
|
||||
<%= link_to_page @revision.author %>
|
||||
</div>
|
||||
|
||||
<div class="navigation navfoot">
|
||||
<%= raw navigation_menu_for_revision.join(' | ') %>
|
||||
<span class="views">
|
||||
| View:
|
||||
<%= link_to 'Source', {:web => @web.address, :action => 'source', :id => @page.name, :rev => @revision_number},
|
||||
{:id => 'view_source', :rel => 'nofollow' } %>
|
||||
</span>
|
||||
<%= render :partial => 'inbound_links' %>
|
||||
<div class="navigation">
|
||||
|
||||
<% if @revision.next_revision %>
|
||||
<% if @revision.next_revision.number < (@page.revisions.length - 1) %>
|
||||
<%= link_to('Forward in time',
|
||||
{: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 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>
|
||||
|
||||
<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>
|
||||
|
|
|
@ -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
|
||||
@hide_navigation = true
|
||||
-%>
|
||||
%>
|
||||
|
||||
<div id="MarkupHelp">
|
||||
<%= render(:file => "#{@web.markup}_help") -%>
|
||||
<%= render(:file => 'wiki_words_help') unless @web.brackets_only? -%>
|
||||
</div>
|
||||
<%= "<p style='color:red'>Please correct the error that caused this error in rendering:<br/><small>#{@params["msg"]}</small></p>" if @params["msg"] %>
|
||||
|
||||
<% form_tag({:web => @web.address, :action => 'save', :id => @page.name},
|
||||
{ :id => 'editForm', :method => 'post', :onsubmit => 'cleanAuthorName();',
|
||||
'accept-charset' => 'utf-8' }) do %>
|
||||
<div>
|
||||
<textarea name="content" id="content" rows="24" cols="60"><%= h(@revision.content.purify) %></textarea>
|
||||
<div id="editFormButtons">
|
||||
<input type="submit" value="Update" accesskey="u" /> as
|
||||
<input type="text" name="author" id="authorName" value="<%= h(@author.purify) %>"
|
||||
onclick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
|
||||
|
|
||||
<span>
|
||||
<%= link_to('Cancel', {:web => @web.address, :action => 'cancel_edit', :id => @page.name},
|
||||
{:accesskey => 'c'}) %>
|
||||
<span class="unlock">(unlocks page)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<%- end -%>
|
||||
<%= form_tag({:web => @web.address, :action => 'save', :id => @page.name},
|
||||
{:id => 'editForm', :method => 'post', :onSubmit => 'cleanAuthorName();'})
|
||||
%>
|
||||
<p>
|
||||
<textarea name="content" style="font-size: 12px; width: 450px; height: 500px"><%= @revision.content %></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" value="Update" accesskey="u" /> as
|
||||
<input type="text" name="author" id="authorName" value="<%= @author %>"
|
||||
onClick="this.value == 'AnonymousCoward' ? this.value = '' : true" />
|
||||
|
|
||||
<% link_to('Cancel',
|
||||
{:web => @web.address, :action => 'cancel_edit', :id => @page.name},
|
||||
{:accesskey => 'c'})
|
||||
%>
|
||||
<small>(unlocks page)</small>
|
||||
</p>
|
||||
<%= end_form_tag %>
|
||||
|
||||
<script type="text/javascript">
|
||||
<%= render("#{@web.markup}_help") if @web %>
|
||||
|
||||
<script language="JavaScript1.2">
|
||||
function cleanAuthorName() {
|
||||
if (document.getElementById('authorName').value == "") {
|
||||
document.getElementById('authorName').value = 'AnonymousCoward';
|
||||
|
|
22
app/views/wiki/rss_feed.rhtml
Normal file
22
app/views/wiki/rss_feed.rhtml
Normal 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>
|
|
@ -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>
|
|
@ -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>
|
||||
<ul>
|
||||
<%- for page in @title_results -%>
|
||||
<% for page in @title_results %>
|
||||
<li>
|
||||
<%= link_to page.plain_name, :web => @web.address, :action => 'show', :id => page.name %>
|
||||
</li>
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
</ul>
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
|
||||
|
||||
<%- unless @results.empty? -%>
|
||||
<% unless @results.empty? %>
|
||||
<h2> <%= @results.length %> page(s) containing search string in the page text:</h2>
|
||||
<ul>
|
||||
<%- for page in @results -%>
|
||||
<% for page in @results %>
|
||||
<li>
|
||||
<%= link_to page.plain_name, :web => @web.address, :action => 'show', :id => page.name %>
|
||||
</li>
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
</ul>
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
|
||||
<%- if (@results + @title_results).empty? -%>
|
||||
<h2>No pages contain "<%= h(@query).html_safe %>" </h2>
|
||||
<% if (@results + @title_results).empty? %>
|
||||
<h2>No pages contain "<%= @params["query"] %>" </h2>
|
||||
<p>
|
||||
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
|
||||
words in separation — only as a sentence fragment.
|
||||
words in separation—only as a sentence fragment.
|
||||
</p>
|
||||
<p>
|
||||
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
|
||||
"[a-z]*Leet?RegExpSkill(s|z)"
|
||||
</p>
|
||||
<p>
|
||||
<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 -%>
|
||||
<% end %>
|
||||
|
|
|
@ -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>
|
|
@ -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 %>}
|
||||
|
||||
<%= @tex_content.html_safe %>
|
||||
<%= @tex_content %>
|
||||
|
||||
\end{document}
|
|
@ -1 +0,0 @@
|
|||
<%= @tex_content.html_safe %>
|
|
@ -1,11 +1,19 @@
|
|||
\documentclass[12pt,titlepage]{article}
|
||||
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amsfonts}
|
||||
\usepackage{fancyhdr}
|
||||
\pagestyle{fancy}
|
||||
|
||||
\fancyhead[LE,RO]{}
|
||||
\fancyhead[LO,RE]{\nouppercase{\bfseries \leftmark}}
|
||||
\fancyfoot[C]{\thepage}
|
||||
|
||||
\usepackage[danish]{babel} %danske tekster
|
||||
\usepackage{a4}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{ucs}
|
||||
\usepackage[utf8x]{inputenc}
|
||||
\usepackage{hyperref}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\input epsf
|
||||
|
||||
|
||||
%-------------------------------------------------------------------
|
||||
|
||||
|
@ -18,6 +26,8 @@
|
|||
\tableofcontents
|
||||
\pagebreak
|
||||
|
||||
\sloppy
|
||||
|
||||
%-------------------------------------------------------------------
|
||||
|
||||
<%= @tex_content %>
|
||||
|
|
|
@ -1,28 +1,18 @@
|
|||
<%- @title = "Wiki webs" -%>
|
||||
<% @title = "Wiki webs" %>
|
||||
|
||||
<ul>
|
||||
<%- @webs.each do |web| -%>
|
||||
|
||||
<li>
|
||||
<%- if web.password -%>
|
||||
<div class="web_protected">
|
||||
<%- else -%>
|
||||
<div class="web_normal">
|
||||
<%- end -%>
|
||||
<%= link_to(web.name, {:web => web.address, :action => 'show', :id => 'HomePage'}) %>
|
||||
<%- if web.published? -%>
|
||||
(<%= link_to_page 'HomePage', web, 'published version', :mode => 'publish' %>)
|
||||
<%- end -%>
|
||||
|
||||
<% @webs.each do |web| %>
|
||||
<li>
|
||||
<% if web.published %>
|
||||
<%= link_to_page 'HomePage', web, web.name, :mode => 'publish' %>
|
||||
(read-only) /
|
||||
<%= link_to_page 'HomePage', web, 'editable version', :mode => 'show' %> (requires login)
|
||||
<% else %>
|
||||
<%= link_to_page 'HomePage', web, web.name, :mode => 'show' %>
|
||||
<% end %>
|
||||
<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 %>
|
||||
- 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 -%>
|
||||
<%= web.pages.length %> pages by <%= web.authors.length %> authors
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<%- end -%>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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; φ, double-struck 𝔸, numeric 𝔸 ⁗, uppercase ™ <</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 <</p>"
|
||||
output2 = "<p>Greek \317\225 \317\206, double-struck \360\235\224\270, numeric 𝔸 ⁗, uppercase \342\204\242 <</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 & \302ivory</p>"
|
||||
output = "<p>\357\277\275elephant & \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 <bad>bar</bad> baz</#{tag_name.downcase}>"
|
||||
xhtmloutput = "<#{tag_name} title='1'>foo <bad>bar</bad> baz</#{tag_name}>"
|
||||
rexmloutput = xhtmloutput
|
||||
|
||||
if %w[caption colgroup optgroup option tbody td tfoot th thead tr].include?(tag_name)
|
||||
htmloutput = "foo <bad>bar</bad> baz"
|
||||
xhtmloutput = htmloutput
|
||||
elsif tag_name == 'col'
|
||||
htmloutput = "foo <bad>bar</bad> baz"
|
||||
xhtmloutput = htmloutput
|
||||
rexmloutput = "<col title='1' />"
|
||||
elsif tag_name == 'table'
|
||||
htmloutput = "foo <bad>bar</bad>baz<table title='1'> </table>"
|
||||
xhtmloutput = htmloutput
|
||||
elsif tag_name == 'image'
|
||||
htmloutput = "<img title='1'/>foo <bad>bar</bad> baz"
|
||||
xhtmloutput = htmloutput
|
||||
rexmloutput = "<image title='1'>foo <bad>bar</bad> baz</image>"
|
||||
elsif VOID_ELEMENTS.include?(tag_name)
|
||||
htmloutput = "<#{tag_name} title='1'/>foo <bad>bar</bad> 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 = "<#{tag_name.upcase} title=\"1\">foo <bad>bar</bad> baz</#{tag_name.upcase}>"
|
||||
xhtmloutput = "<#{tag_name.upcase} title='1'>foo <bad>bar</bad> baz</#{tag_name.upcase}>"
|
||||
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 <bad>bar</bad> baz</p>"
|
||||
htmloutput = "<p #{attribute_name.downcase}='foo'>foo <bad>bar</bad> 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 <bad>bar</bad> 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 = "<#{tag_name.downcase} xlink:href='#foo'/>"
|
||||
xhtmloutput = "<#{tag_name} xlink:href='#foo'></#{tag_name}>"
|
||||
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 = "<#{tag_name.downcase} xlink:href='\n#foo'/>"
|
||||
xhtmloutput = "<#{tag_name} xlink:href='\n#foo'></#{tag_name}>"
|
||||
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>𝒵 𝔸</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
|
10
attic/vendor/plugins/HTML5lib/History.txt
vendored
10
attic/vendor/plugins/HTML5lib/History.txt
vendored
|
@ -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!
|
||||
|
17
attic/vendor/plugins/HTML5lib/LICENSE
vendored
17
attic/vendor/plugins/HTML5lib/LICENSE
vendored
|
@ -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.
|
116
attic/vendor/plugins/HTML5lib/Manifest.txt
vendored
116
attic/vendor/plugins/HTML5lib/Manifest.txt
vendored
|
@ -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
|
45
attic/vendor/plugins/HTML5lib/README
vendored
45
attic/vendor/plugins/HTML5lib/README
vendored
|
@ -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.
|
33
attic/vendor/plugins/HTML5lib/Rakefile.rb
vendored
33
attic/vendor/plugins/HTML5lib/Rakefile.rb
vendored
|
@ -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
|
5
attic/vendor/plugins/HTML5lib/bin/html5
vendored
5
attic/vendor/plugins/HTML5lib/bin/html5
vendored
|
@ -1,5 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'html5/cli'
|
||||
|
||||
HTML5::CLI.run
|
13
attic/vendor/plugins/HTML5lib/lib/html5.rb
vendored
13
attic/vendor/plugins/HTML5lib/lib/html5.rb
vendored
|
@ -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
|
248
attic/vendor/plugins/HTML5lib/lib/html5/cli.rb
vendored
248
attic/vendor/plugins/HTML5lib/lib/html5/cli.rb
vendored
|
@ -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
|
1047
attic/vendor/plugins/HTML5lib/lib/html5/constants.rb
vendored
1047
attic/vendor/plugins/HTML5lib/lib/html5/constants.rb
vendored
File diff suppressed because it is too large
Load diff
|
@ -1,10 +0,0 @@
|
|||
require 'delegate'
|
||||
require 'enumerator'
|
||||
|
||||
module HTML5
|
||||
module Filters
|
||||
class Base < SimpleDelegator
|
||||
include Enumerable
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -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
Loading…
Reference in a new issue