wiki 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +29 -0
  6. data/Rakefile +1 -0
  7. data/lib/wiki.rb +2 -0
  8. data/lib/wiki/ReadMe.md +89 -0
  9. data/lib/wiki/config.ru +2 -0
  10. data/lib/wiki/favicon.rb +31 -0
  11. data/lib/wiki/page.rb +74 -0
  12. data/lib/wiki/random_id.rb +5 -0
  13. data/lib/wiki/server.rb +336 -0
  14. data/lib/wiki/server_helpers.rb +66 -0
  15. data/lib/wiki/stores/ReadMe.md +26 -0
  16. data/lib/wiki/stores/all.rb +3 -0
  17. data/lib/wiki/stores/couch.rb +121 -0
  18. data/lib/wiki/stores/file.rb +53 -0
  19. data/lib/wiki/stores/store.rb +38 -0
  20. data/lib/wiki/version.rb +3 -0
  21. data/lib/wiki/views/client/Gruntfile.js +50 -0
  22. data/lib/wiki/views/client/ReadMe.md +67 -0
  23. data/lib/wiki/views/client/build-test.bat +10 -0
  24. data/lib/wiki/views/client/build.bat +8 -0
  25. data/lib/wiki/views/client/builder.pl +41 -0
  26. data/lib/wiki/views/client/client.coffee +3 -0
  27. data/lib/wiki/views/client/client.js +3607 -0
  28. data/lib/wiki/views/client/crosses.png +0 -0
  29. data/lib/wiki/views/client/images/external-link-ltr-icon.png +0 -0
  30. data/lib/wiki/views/client/images/noise.png +0 -0
  31. data/lib/wiki/views/client/images/oops.jpg +0 -0
  32. data/lib/wiki/views/client/js/d3/d3.behavior.js +198 -0
  33. data/lib/wiki/views/client/js/d3/d3.chart.js +984 -0
  34. data/lib/wiki/views/client/js/d3/d3.csv.js +92 -0
  35. data/lib/wiki/views/client/js/d3/d3.geo.js +566 -0
  36. data/lib/wiki/views/client/js/d3/d3.geom.js +825 -0
  37. data/lib/wiki/views/client/js/d3/d3.js +3597 -0
  38. data/lib/wiki/views/client/js/d3/d3.layout.js +1923 -0
  39. data/lib/wiki/views/client/js/d3/d3.time.js +660 -0
  40. data/lib/wiki/views/client/js/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  41. data/lib/wiki/views/client/js/images/ui-icons_222222_256x240.png +0 -0
  42. data/lib/wiki/views/client/js/jquery-1.6.2.min.js +18 -0
  43. data/lib/wiki/views/client/js/jquery-1.7.1.min.js +4 -0
  44. data/lib/wiki/views/client/js/jquery-1.9.1.min.js +5 -0
  45. data/lib/wiki/views/client/js/jquery-migrate-1.1.1.min.js +3 -0
  46. data/lib/wiki/views/client/js/jquery-ui-1.10.1.custom.min.css +5 -0
  47. data/lib/wiki/views/client/js/jquery-ui-1.10.1.custom.min.js +6 -0
  48. data/lib/wiki/views/client/js/jquery-ui-1.8.16.custom.css +339 -0
  49. data/lib/wiki/views/client/js/jquery-ui-1.8.16.custom.min.js +315 -0
  50. data/lib/wiki/views/client/js/jquery.ie.cors.js +310 -0
  51. data/lib/wiki/views/client/js/jquery.ui.touch-punch.min.js +11 -0
  52. data/lib/wiki/views/client/js/modernizr.custom.63710.js +824 -0
  53. data/lib/wiki/views/client/js/sockjs-0.3.min.js +27 -0
  54. data/lib/wiki/views/client/js/underscore-min.js +30 -0
  55. data/lib/wiki/views/client/mkplugin.sh +97 -0
  56. data/lib/wiki/views/client/package.json +36 -0
  57. data/lib/wiki/views/client/runtests.html +26 -0
  58. data/lib/wiki/views/client/style.css +339 -0
  59. data/lib/wiki/views/client/test/mocha.css +231 -0
  60. data/lib/wiki/views/client/test/mocha.js +5340 -0
  61. data/lib/wiki/views/client/test/testclient.js +17133 -0
  62. data/lib/wiki/views/client/testclient.coffee +18 -0
  63. data/lib/wiki/views/client/theme/granite.css +59 -0
  64. data/lib/wiki/views/client/theme/stoneSeamless.jpg +0 -0
  65. data/lib/wiki/views/client/twitter-maintainance.jpg +0 -0
  66. data/lib/wiki/views/layout.haml +56 -0
  67. data/lib/wiki/views/oops.haml +5 -0
  68. data/lib/wiki/views/page.haml +20 -0
  69. data/lib/wiki/views/static.html +30 -0
  70. data/lib/wiki/views/view.haml +2 -0
  71. data/wiki.gemspec +28 -0
  72. metadata +121 -0
@@ -0,0 +1,66 @@
1
+ module ServerHelpers
2
+
3
+ def cross_origin
4
+ headers 'Access-Control-Allow-Origin' => "*" if request.env['HTTP_ORIGIN']
5
+ end
6
+
7
+ def resolve_links string
8
+ string.
9
+ gsub(/\[\[([^\]]+)\]\]/i) {
10
+ |name|
11
+ name.gsub!(/^\[\[(.*)\]\]/, '\1')
12
+
13
+ slug = name.gsub(/\s/, '-')
14
+ slug = slug.gsub(/[^A-Za-z0-9-]/, '').downcase
15
+ '<a class="internal" href="/'+slug+'.html" data-page-name="'+slug+'" >'+name+'</a>'
16
+ }.
17
+ gsub(/\[(http.*?) (.*?)\]/i, '<a class="external" href="\1" rel="nofollow">\2</a>')
18
+ end
19
+
20
+ def openid_consumer
21
+ @openid_consumer ||= OpenID::Consumer.new(session, OpenID::Store::Filesystem.new("#{farm_status}/tmp/openid"))
22
+ end
23
+
24
+ def authenticated?
25
+ session[:authenticated] == true
26
+ end
27
+
28
+ def identified?
29
+ Store.exists? "#{farm_status}/open_id.identifier"
30
+ end
31
+
32
+ def claimed?
33
+ Store.exists? "#{farm_status}/open_id.identity"
34
+ end
35
+
36
+ def authenticate!
37
+ session[:authenticated] = true
38
+ redirect "/"
39
+ end
40
+
41
+ def oops status, message
42
+ haml :oops, :layout => false, :locals => {:status => status, :message => message}
43
+ end
44
+
45
+ def serve_resources_locally?(site)
46
+ !!ENV['FARM_DOMAINS'] && ENV['FARM_DOMAINS'].split(',').any?{|domain| site.end_with?(domain)}
47
+ end
48
+
49
+ def serve_page(name, site=request.host)
50
+ cross_origin
51
+ halt 404 unless farm_page(site).exists?(name)
52
+ JSON.pretty_generate farm_page(site).get(name)
53
+ end
54
+
55
+ def synopsis page
56
+ text = page['synopsis']
57
+ p1 = page['story'] && page['story'][0]
58
+ p2 = page['story'] && page['story'][1]
59
+ text ||= p1 && p1['text'] if p1 && p1['type'] == 'paragraph'
60
+ text ||= p2 && p2['text'] if p2 && p2['type'] == 'paragraph'
61
+ text ||= p1 && p1['text'] || p2 && p2['text'] || page['story'] && "A page with #{page['story'].length} paragraphs." || "A page with no story."
62
+ return text
63
+ end
64
+
65
+ end
66
+
@@ -0,0 +1,26 @@
1
+ We support several wiki page persistence mechanisms called Stores.
2
+ Currently the server includes all versions and selects one with
3
+ an environment variable.
4
+
5
+ File Store
6
+ ==========
7
+
8
+ Pages are stored in flat files under `data` in the subdirectory
9
+ `pages`. File names are the slugs with no suffix.
10
+ A second subdirectory, `status`, contains additional metadata
11
+ such as the site's favicon.png.
12
+
13
+ When the server is operated as a wiki site farm,
14
+ data and status subdirectories are pushed several levels deeper
15
+ in the file hierarchy under `data/farm/*` where * is replaced
16
+ with the virtual host domain name.
17
+ The existence of the farm subdirectory configures the server
18
+ into farm mode.
19
+
20
+ Couch Store
21
+ ===========
22
+
23
+ Pages are stored as Couch documents with fully qualified
24
+ names following the conventions established in the File Store.
25
+ An environment variable indicates that the server should
26
+ be in farm mode.
@@ -0,0 +1,3 @@
1
+ require File.expand_path('store', File.dirname(__FILE__))
2
+ require File.expand_path('file', File.dirname(__FILE__))
3
+ require File.expand_path('couch', File.dirname(__FILE__))
@@ -0,0 +1,121 @@
1
+ require 'time' # for Time#iso8601
2
+
3
+ class CouchStore < Store
4
+ class << self
5
+
6
+ attr_writer :db # used by specs
7
+
8
+ def db
9
+ unless @db
10
+ couchdb_server = ENV['COUCHDB_URL'] || raise('please set ENV["COUCHDB_URL"]')
11
+ @db = CouchRest.database!("#{couchdb_server}/sfw")
12
+ begin
13
+ @db.save_doc "_id" => "_design/recent-changes", :views => {}
14
+ rescue RestClient::Conflict
15
+ # design document already exists, do nothing
16
+ end
17
+ end
18
+ @db
19
+ end
20
+
21
+ ### GET
22
+
23
+ def get_text(path)
24
+ path = relative_path(path)
25
+ begin
26
+ db.get(path)['data']
27
+ rescue RestClient::ResourceNotFound
28
+ nil
29
+ end
30
+ end
31
+
32
+ def get_blob(path)
33
+ blob = get_text path
34
+ Base64.decode64 blob if blob
35
+ end
36
+
37
+ ### PUT
38
+
39
+ def put_text(path, text, metadata={})
40
+ path = relative_path(path)
41
+ metadata = metadata.each{ |k,v| metadata[k] = relative_path(v) }
42
+ attrs = {
43
+ 'data' => text,
44
+ 'updated_at' => Time.now.utc.iso8601
45
+ }.merge! metadata
46
+
47
+ begin
48
+ db.save_doc attrs.merge('_id' => path)
49
+ rescue RestClient::Conflict
50
+ doc = db.get path
51
+ doc.merge! attrs
52
+ doc.save
53
+ end
54
+ text
55
+ end
56
+
57
+ def put_blob(path, blob)
58
+ put_text path, Base64.strict_encode64(blob)
59
+ blob
60
+ end
61
+
62
+ ### COLLECTIONS
63
+
64
+ def annotated_pages(pages_dir)
65
+ changes = pages pages_dir
66
+ changes.map do |change|
67
+ page = JSON.parse change['value']['data']
68
+ page.merge! 'updated_at' => Time.parse(change['value']['updated_at'])
69
+ page.merge! 'name' => change['value']['name']
70
+ page
71
+ end
72
+ end
73
+
74
+ ### UTILITY
75
+
76
+ def pages(pages_dir)
77
+ pages_dir = relative_path pages_dir
78
+ pages_dir_safe = CGI.escape pages_dir
79
+ begin
80
+ db.view("recent-changes/#{pages_dir_safe}")['rows']
81
+ rescue RestClient::ResourceNotFound
82
+ create_view 'recent-changes', pages_dir
83
+ db.view("recent-changes/#{pages_dir_safe}")['rows']
84
+ end
85
+ end
86
+
87
+ def create_view(design_name, view_name)
88
+ design = db.get "_design/#{design_name}"
89
+ design['views'][view_name] = {
90
+ :map => "
91
+ function(doc) {
92
+ if (doc.directory == '#{view_name}')
93
+ emit(doc._id, doc)
94
+ }
95
+ "
96
+ }
97
+ design.save
98
+ end
99
+
100
+ def farm?(_)
101
+ !!ENV['FARM_MODE']
102
+ end
103
+
104
+ def mkdir(_)
105
+ # do nothing
106
+ end
107
+
108
+ def exists?(path)
109
+ !(get_text path).nil?
110
+ end
111
+
112
+ def relative_path(path)
113
+ raise "Please set @app_root" unless @app_root
114
+ path.match(%r[^#{Regexp.escape @app_root}/?(.+?)$]) ? $1 : path
115
+ end
116
+
117
+ end
118
+
119
+ end
120
+
121
+
@@ -0,0 +1,53 @@
1
+ class FileStore < Store
2
+ class << self
3
+
4
+ ### GET
5
+
6
+ def get_text(path)
7
+ File.read path if File.exist? path
8
+ end
9
+
10
+ def get_blob(path)
11
+ File.binread path if File.exist? path
12
+ end
13
+
14
+ ### PUT
15
+
16
+ def put_text(path, text, metadata=nil)
17
+ # Note: metadata is ignored for filesystem storage
18
+ File.open(path, 'w'){ |file| file.write text }
19
+ text
20
+ end
21
+
22
+ def put_blob(path, blob)
23
+ File.open(path, 'wb'){ |file| file.write blob }
24
+ blob
25
+ end
26
+
27
+ ### COLLECTIONS
28
+
29
+ def annotated_pages(pages_dir)
30
+ Dir.foreach(pages_dir).reject{|name|name =~ /^\./}.collect do |name|
31
+ page = get_page(File.join pages_dir, name)
32
+ page.merge!({
33
+ 'name' => name,
34
+ 'updated_at' => File.new("#{pages_dir}/#{name}").mtime
35
+ })
36
+ end
37
+ end
38
+
39
+ ### UTILITY
40
+
41
+ def farm?(data_root)
42
+ ENV['FARM_MODE'] || File.exists?(File.join data_root, "farm")
43
+ end
44
+
45
+ def mkdir(directory)
46
+ FileUtils.mkdir_p directory
47
+ end
48
+
49
+ def exists?(path)
50
+ File.exists?(path)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,38 @@
1
+ class Store
2
+ class << self
3
+
4
+ attr_writer :app_root
5
+
6
+ def set(store_classname, app_root)
7
+ # @store_class is literally the class FileStore by default, or if a class name is passed in, another subclass of Store
8
+ @store_class = store_classname ? Kernel.const_get(store_classname) : FileStore
9
+ @store_class.app_root = app_root
10
+ @store_class
11
+ end
12
+
13
+ def method_missing(*args)
14
+ # For any method not implemented in *this* class, pass the method call through to the designated Store subclass
15
+ @store_class.send(*args)
16
+ end
17
+
18
+ ### GET
19
+
20
+ def get_hash(path)
21
+ json = get_text path
22
+ JSON.parse json if json
23
+ end
24
+
25
+ alias_method :get_page, :get_hash
26
+
27
+ ### PUT
28
+
29
+ def put_hash(path, ruby_data, metadata={})
30
+ json = JSON.pretty_generate(ruby_data)
31
+ put_text path, json, metadata
32
+ ruby_data
33
+ end
34
+
35
+ alias_method :put_page, :put_hash
36
+
37
+ end
38
+ end
@@ -0,0 +1,3 @@
1
+ module Wiki
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,50 @@
1
+ module.exports = function (grunt) {
2
+ grunt.loadNpmTasks('grunt-browserify');
3
+ grunt.loadNpmTasks('grunt-contrib-coffee');
4
+ grunt.loadNpmTasks('grunt-contrib-watch');
5
+
6
+ grunt.initConfig({
7
+ browserify: {
8
+ client: {
9
+ src: ['client.coffee'],
10
+ dest: 'client.js',
11
+ options: {
12
+ transform: ['coffeeify'],
13
+ debug: true
14
+ }
15
+ },
16
+ testClient: {
17
+ src: ['testclient.coffee', 'plugins/*/test.coffee'],
18
+ dest: 'test/testclient.js',
19
+ options: {
20
+ transform: ['coffeeify'],
21
+ debug: true
22
+ }
23
+ }
24
+ },
25
+
26
+ coffee: {
27
+ plugins: {
28
+ expand: true,
29
+ src: ['plugins/**/*.coffee'],
30
+ ext: '.js'
31
+ }
32
+ },
33
+
34
+ watch: {
35
+ all: {
36
+ files: [
37
+ '<%= browserify.testClient.src %>',
38
+ '<%= browserify.client.src %>',
39
+ '<%= coffee.plugins.src %>',
40
+ 'lib/**/*.coffee'
41
+ ],
42
+ tasks: ['browserify', 'coffee']
43
+ }
44
+ }
45
+ });
46
+
47
+ grunt.registerTask('build', ['coffee', 'browserify']);
48
+ grunt.registerTask('default', ['build']);
49
+
50
+ };
@@ -0,0 +1,67 @@
1
+ Client Goals
2
+ ============
3
+
4
+ A server offers direct restful read/write access to pages it owns and proxy access to pages held elsewhere in federated space.
5
+ A page is owned if it was created with the server or has been cloned and edited such that it is believed to be the most authoritative copy of a page previously owned elsewhere.
6
+ A server operates as a proxy to the rest of the federated wiki.
7
+ In this role it reformats data and metadata providing a unified experience.
8
+ It is welcome to collect behavioral statistics in order to improve this experience by anticipating permitted peer-to-peer server operations.
9
+
10
+ In summary, the server's client side exists to:
11
+
12
+ * Offer to a user a browsing experience that is independent of any specific server.
13
+ * Support writing, editing and curating of one server in a way that offers suitable influence over others.
14
+
15
+ Working with Browserify
16
+ =======================
17
+
18
+ The client side is written in CoffeeScript, and built with Browserify.
19
+ If you are not checking in changes you need not concern yourself with this.
20
+ We've checked in the generated Javascript for the client application.
21
+
22
+ If you do want to check in changes, install node v0.6.x
23
+
24
+ * On Linux download the source from [GitHub](https://github.com/joyent/node)
25
+ * On Windows get the installer from the [main node.js site](http://nodejs.org).
26
+ * On Mac you should be able to choose either.
27
+
28
+ Once node is installed come back to this directory and run:
29
+
30
+ * `npm install` To install CoffeeScript, Browserify, and all their dependencies.
31
+
32
+ You can now use:
33
+
34
+ * `npm start` To build the main client.
35
+ * `npm test` To build the test client.
36
+
37
+ These commands build client.js and test/testclient.js from client.coffee and
38
+ testclient.coffee respectively. They use their entry files to require the
39
+ rest of the coffee script they need from the source CS files in /lib.
40
+
41
+ We also have a cool automated talking (Mac only) Perl build script that uses
42
+ a globally installed browserify via `npm install -g browserify`, it watches
43
+ for changes, builds the clients automatically, and gives a verbal report
44
+ when you have syntax errors.
45
+
46
+ Testing
47
+ =======
48
+
49
+ All the client tests can be run by visiting /runtests.html on your server
50
+ or by running the full ruby test suite. Information about the libraries we
51
+ are using for testing can be found at:
52
+
53
+ * http://visionmedia.github.com/mocha/
54
+ * https://github.com/LearnBoost/expect.js
55
+ * http://sinonjs.org/
56
+
57
+ CoffeeScript hints
58
+ ==================
59
+
60
+ We recommend taking time to learn the CoffeeScript syntax and the rationale for the Javascript idioms it employs. Start here:
61
+
62
+ http://jashkenas.github.com/coffee-script/
63
+
64
+ We used a Javascript to Coffeescript converter to create the first draft of client.coffee. You may find this converter useful for importing sample codes.
65
+
66
+ http://ricostacruz.com/js2coffee/
67
+
@@ -0,0 +1,10 @@
1
+ @ECHO OFF
2
+ ::
3
+ :: Used on Windows to build testclient.js as npm start and test don't work!
4
+ ::
5
+
6
+ :: Build testclient.js - need to expand .\plugins\*\test.coffee as wildcard does not work on Windows
7
+
8
+ echo "Building test\testclient.js"
9
+
10
+ .\node_modules\.bin\browserify.cmd testclient.coffee .\plugins\calendar\test.coffee .\plugins\changes\test.coffee .\plugins\efficiency\test.coffee .\plugins\report\test.coffee .\plugins\txtzyme\test.coffee -o test\testclient.js