wiki 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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