sharkey-web 3.3.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.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/Gemfile +4 -0
- data/LICENSE.md +19 -0
- data/README.md +188 -0
- data/Rakefile +8 -0
- data/bin/sharkey-web +9 -0
- data/config.ru +3 -0
- data/lib/sharkey.rb +12 -0
- data/lib/sharkey/app.rb +526 -0
- data/lib/sharkey/importerexporter.rb +79 -0
- data/lib/sharkey/models.rb +295 -0
- data/lib/sharkey/public/css/loading.gif +0 -0
- data/lib/sharkey/public/css/magicsuggest.css +232 -0
- data/lib/sharkey/public/css/nprogress.css +74 -0
- data/lib/sharkey/public/css/styles.css +263 -0
- data/lib/sharkey/public/css/ui.fancytree.css +545 -0
- data/lib/sharkey/public/data/sentences.txt +5 -0
- data/lib/sharkey/public/fonts/Quadrata.eot +0 -0
- data/lib/sharkey/public/fonts/Quadrata.svg +613 -0
- data/lib/sharkey/public/fonts/Quadrata.ttf +0 -0
- data/lib/sharkey/public/fonts/Quadrata.woff +0 -0
- data/lib/sharkey/public/fonts/Quadrata.zip +0 -0
- data/lib/sharkey/public/images/loader.gif +0 -0
- data/lib/sharkey/public/images/sharkey-logo.png +0 -0
- data/lib/sharkey/public/images/sharkey.png +0 -0
- data/lib/sharkey/public/js/ajaxmanager.js +67 -0
- data/lib/sharkey/public/js/keybindings.js +92 -0
- data/lib/sharkey/public/js/lib/bootstrap.min.js +6 -0
- data/lib/sharkey/public/js/lib/jquery-1.9.1.min.js +5 -0
- data/lib/sharkey/public/js/lib/jquery-ui.js +16150 -0
- data/lib/sharkey/public/js/lib/jquery.bootstrap-autohidingnavbar.js +213 -0
- data/lib/sharkey/public/js/lib/jquery.fancytree-all.js +6424 -0
- data/lib/sharkey/public/js/lib/jquery.tagcloud.js +92 -0
- data/lib/sharkey/public/js/lib/magicsuggest.js +1468 -0
- data/lib/sharkey/public/js/lib/mousetrap.min.js +9 -0
- data/lib/sharkey/public/js/lib/nprogress.js +476 -0
- data/lib/sharkey/public/js/page-add-link-autofill.js +102 -0
- data/lib/sharkey/public/js/page-add-link.js +156 -0
- data/lib/sharkey/public/js/page-categories.js +348 -0
- data/lib/sharkey/public/js/page-edit-link.js +103 -0
- data/lib/sharkey/public/js/page-links.js +54 -0
- data/lib/sharkey/public/js/page-settings.js +93 -0
- data/lib/sharkey/public/js/page-tagcloud.js +35 -0
- data/lib/sharkey/public/js/page-tags.js +287 -0
- data/lib/sharkey/public/js/scripts.js +147 -0
- data/lib/sharkey/public/themes/amelia/style.css +7 -0
- data/lib/sharkey/public/themes/bootstrap/style.css +5785 -0
- data/lib/sharkey/public/themes/cerulean/style.css +7 -0
- data/lib/sharkey/public/themes/cosmo/style.css +7 -0
- data/lib/sharkey/public/themes/cyborg/style.css +7 -0
- data/lib/sharkey/public/themes/darkly/style.css +7 -0
- data/lib/sharkey/public/themes/facebook-like/README.md +6 -0
- data/lib/sharkey/public/themes/facebook-like/style.css +6085 -0
- data/lib/sharkey/public/themes/flatly/style.css +7 -0
- data/lib/sharkey/public/themes/fonts/glyphicons-halflings-regular.eot +0 -0
- data/lib/sharkey/public/themes/fonts/glyphicons-halflings-regular.svg +229 -0
- data/lib/sharkey/public/themes/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/lib/sharkey/public/themes/fonts/glyphicons-halflings-regular.woff +0 -0
- data/lib/sharkey/public/themes/holo-like/README.md +5 -0
- data/lib/sharkey/public/themes/holo-like/style.css +5997 -0
- data/lib/sharkey/public/themes/journal/style.css +7 -0
- data/lib/sharkey/public/themes/lumen/style.css +7 -0
- data/lib/sharkey/public/themes/readable/style.css +7 -0
- data/lib/sharkey/public/themes/simplex/style.css +7 -0
- data/lib/sharkey/public/themes/slate/style.css +7 -0
- data/lib/sharkey/public/themes/spacelab/style.css +7 -0
- data/lib/sharkey/public/themes/superhero/style.css +7 -0
- data/lib/sharkey/public/themes/united/style.css +7 -0
- data/lib/sharkey/public/themes/yeti/style.css +7 -0
- data/lib/sharkey/setting.rb +74 -0
- data/lib/sharkey/version.rb +5 -0
- data/lib/sharkey/views/404.slim +4 -0
- data/lib/sharkey/views/about.slim +7 -0
- data/lib/sharkey/views/add_link.slim +157 -0
- data/lib/sharkey/views/categories.slim +101 -0
- data/lib/sharkey/views/category.slim +22 -0
- data/lib/sharkey/views/centered.slim +49 -0
- data/lib/sharkey/views/dashboard.slim +107 -0
- data/lib/sharkey/views/dashboard_index.slim +26 -0
- data/lib/sharkey/views/edit_link.slim +121 -0
- data/lib/sharkey/views/help.slim +74 -0
- data/lib/sharkey/views/keybindings.slim +58 -0
- data/lib/sharkey/views/link.slim +30 -0
- data/lib/sharkey/views/links.slim +22 -0
- data/lib/sharkey/views/navbar.slim +68 -0
- data/lib/sharkey/views/settings.slim +74 -0
- data/lib/sharkey/views/settings_index.slim +220 -0
- data/lib/sharkey/views/single_category.slim +37 -0
- data/lib/sharkey/views/single_link.slim +95 -0
- data/lib/sharkey/views/single_tag.slim +33 -0
- data/lib/sharkey/views/tag.slim +22 -0
- data/lib/sharkey/views/tagcloud.slim +54 -0
- data/lib/sharkey/views/tags.slim +99 -0
- data/sharkey-web.gemspec +44 -0
- metadata +324 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: f53d2ea02c3428a8b79fa0d255f81d79df9e5961
|
|
4
|
+
data.tar.gz: b318484b07be8b692ea824d7e472304feb27c160
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b70b6a52387f1bd8f730d277c3eb2239609b6267bf87dc6a2eb958c220317dd351c0128cd2d3893c2f4c3b98125ec955a0bcb71c25a6ee5477dcc92f24052650
|
|
7
|
+
data.tar.gz: 3b16133fe035b6f9a219873c2db09d2056d330e0ccf2d522c86b9eaa6fa14efad4c1fed3ffd85f6431c7ad88fe33f6baecc83b4ae8019e44f6ccab936c70136d
|
data/.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
*.gem
|
|
2
|
+
*.rbc
|
|
3
|
+
.bundle
|
|
4
|
+
.config
|
|
5
|
+
.yardoc
|
|
6
|
+
Gemfile.lock
|
|
7
|
+
InstalledFiles
|
|
8
|
+
_yardoc
|
|
9
|
+
coverage
|
|
10
|
+
doc/
|
|
11
|
+
lib/bundler/man
|
|
12
|
+
pkg
|
|
13
|
+
rdoc
|
|
14
|
+
spec/reports
|
|
15
|
+
test/tmp
|
|
16
|
+
test/version_tmp
|
|
17
|
+
tmp
|
|
18
|
+
*.bundle
|
|
19
|
+
*.so
|
|
20
|
+
*.o
|
|
21
|
+
*.a
|
|
22
|
+
mkmf.log
|
|
23
|
+
*.db
|
|
24
|
+
settings.yml
|
data/Gemfile
ADDED
data/LICENSE.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (C) <2014> by <Alexandre Dantas>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
5
|
+
the Software without restriction, including without limitation the rights to
|
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
8
|
+
so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Sharkey
|
|
2
|
+
|
|
3
|
+
`sharkey` is a cute web-based personal bookmarking service.
|
|
4
|
+
|
|
5
|
+
* **Bookmarking** means it _saves your links_, allows you to _tag them_, arrange
|
|
6
|
+
in _categories_ and, most important of all, _keep track of what you should visit later_.
|
|
7
|
+
* **Personal** means _you control your own data_. Everything is stored on your
|
|
8
|
+
computer and _you can import/export/delete as you please_. It is like the anti-social
|
|
9
|
+
cousin of [Delicious][delicious].
|
|
10
|
+
* **Web-based** means it runs on your browser. _You don't need an internet connection_,
|
|
11
|
+
though! It doesn't mean you access a site to use it - it runs on _your computer_.
|
|
12
|
+
It just happens that I prefer to make sites instead of designing windows and buttons
|
|
13
|
+
and stuff.
|
|
14
|
+
* **Cute** means it has a nice appearance. It comes with _lots of themes_ and you can
|
|
15
|
+
even customize it _with your own themes_ too.
|
|
16
|
+
|
|
17
|
+
Here's some screenshots for you:
|
|
18
|
+
|
|
19
|
+
<!-- Add Screenshots -->
|
|
20
|
+
|
|
21
|
+
As you can see, `sharkey` is a great tool for [tab hoarders][hoard].
|
|
22
|
+
|
|
23
|
+
### Why would you do that?
|
|
24
|
+
|
|
25
|
+
If you know me (which I bet you don't) the following sentence will seem familiar:
|
|
26
|
+
|
|
27
|
+
<blockquote>
|
|
28
|
+
<em>Nah, I won't close this tab... I might need it later.
|
|
29
|
+
Let's open another one instead</em>
|
|
30
|
+
<footer>
|
|
31
|
+
- <cite>Me, Always</cite>
|
|
32
|
+
</footer>
|
|
33
|
+
</blockquote>
|
|
34
|
+
|
|
35
|
+
Being like this for years leaves a nasty trail - lots of Firefox crashes,
|
|
36
|
+
everything getting slow as fuark, computer-restarting paranoia, etc... <br />
|
|
37
|
+
And I bet within those 400+ tabs there are _some_ repeated.
|
|
38
|
+
|
|
39
|
+
Unfortunately most bookmarking services lacked one or another feature;
|
|
40
|
+
some were beautiful but didn't had many things; others had 'em but didn't
|
|
41
|
+
allow you to control your own data; others were super ugly and some
|
|
42
|
+
were very complicated to install.
|
|
43
|
+
|
|
44
|
+
Well, `sharkey` is an attempt to gather the best things from them.
|
|
45
|
+
|
|
46
|
+
You should probably [check out the live demo][live]. <br />
|
|
47
|
+
Be warned, though, that since it's completely public it might have some
|
|
48
|
+
nasty spam or _even worse things_. <br />
|
|
49
|
+
But `sharkey` allows you to easily destroy all data so it shouldn't be
|
|
50
|
+
that much of an issue.
|
|
51
|
+
|
|
52
|
+
### How to use
|
|
53
|
+
|
|
54
|
+
Hey champ, you have to install it first.
|
|
55
|
+
|
|
56
|
+
### How to install
|
|
57
|
+
|
|
58
|
+
You can easily fetch and install `sharkey` via _Ruby Gems_. <br />
|
|
59
|
+
Look at that:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
$ gem install sharkey-web
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
That command takes a while because it install all it's dependencies.
|
|
66
|
+
You can also [grab the gem file here][gem] if you want.
|
|
67
|
+
|
|
68
|
+
But, hey, if you run that command _right now_ it will probably fail.
|
|
69
|
+
That's because you need...
|
|
70
|
+
|
|
71
|
+
### ...a dependency
|
|
72
|
+
|
|
73
|
+
`sharkey` requires `sqlite3` - and not just the library that comes with
|
|
74
|
+
your distro, no; it needs the fancy development package.
|
|
75
|
+
|
|
76
|
+
If you're on Ubuntu/Debian the following command should suffice:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
$ sudo apt-get install libsqlite3-dev
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Other systems may have different names but you get the
|
|
83
|
+
picture - install SQLite!
|
|
84
|
+
|
|
85
|
+
If you're on Windows... Tough luck, I have no idea what you could do. <br />
|
|
86
|
+
If you ever figure it out, please contact me so I can replace this
|
|
87
|
+
shameful paragraph. <br />
|
|
88
|
+
S-sorry, senpai.
|
|
89
|
+
|
|
90
|
+
### How to use... again
|
|
91
|
+
|
|
92
|
+
Alright, so you've installed the dependencies and now you have `sharkey`!
|
|
93
|
+
|
|
94
|
+
Why don't you run it?
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
$ sharkey-web
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
This will create the `sharkey`'s web server, launch it on the background
|
|
101
|
+
and call your web browser. It should open a new tab (yet another...!) with
|
|
102
|
+
that cute interface of the screenshots above.
|
|
103
|
+
|
|
104
|
+
To kill the process, simply run:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
$ sharkey-web --kill
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Anyway, go play with it!
|
|
111
|
+
|
|
112
|
+
Still reading? Go there, I can wait!
|
|
113
|
+
|
|
114
|
+
### And Then...
|
|
115
|
+
|
|
116
|
+
Back, already? ...okay
|
|
117
|
+
|
|
118
|
+
The rest of the document is just some boring technical talk, you should
|
|
119
|
+
probably just stick with the _Help_ session on the `sharkey` web interface.
|
|
120
|
+
|
|
121
|
+
Anyway, good talking to you. See ya!
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
Great, now that that they're gone, let's go to what _really_ matters!
|
|
126
|
+
|
|
127
|
+
`sharkey` is essentially:
|
|
128
|
+
|
|
129
|
+
* A [Ruby][ruby] program;
|
|
130
|
+
* That is a web application, made with [Sinatra][sinatra];
|
|
131
|
+
* Encapsulated on a [Vegas][vegas] executable;
|
|
132
|
+
* That has a GUI made with HTML5/CSS3/Javascript;
|
|
133
|
+
* With a [Bootstrap][bootstrap] layout ([and Bootswatch themes][bootswatch]);
|
|
134
|
+
* Made dynamic and fluid with [jQuery][jquery];
|
|
135
|
+
* And featuring with tons of plugins (see below);
|
|
136
|
+
|
|
137
|
+
Now, between you and me, this was my first attempt on developing a web
|
|
138
|
+
application. It was a project to learn several web technologies and how to
|
|
139
|
+
integrate them. But don't go around telling anyone this!
|
|
140
|
+
|
|
141
|
+
## Credits
|
|
142
|
+
|
|
143
|
+
Besides the links above, I have to point out which tools I've used
|
|
144
|
+
behind the scenes.
|
|
145
|
+
|
|
146
|
+
### Tools
|
|
147
|
+
|
|
148
|
+
* [tux: A console with direct access to Sinatra's internal stuff](https://github.com/cldwalker/tux). I used it all the time to directly access the database and simulate some GET/POST requests.
|
|
149
|
+
* [Mousetrap: a nice keyboard helper](http://craig.is/killing/mice). Thanks to it one can use `sharkey` real fast through keyboard shortcuts.
|
|
150
|
+
* [jquery.tagcloud](https://github.com/addywaddy/jquery.tagcloud.js), a simple plugin to create... tag clouds.
|
|
151
|
+
* [Bootstrap Auto-Hiding Navbar](https://github.com/istvan-ujjmeszaros/bootstrap-autohidingnavbar) - pretty self-explanatory. I like how when you scroll down the navbar hides and when you scroll up it pops up again.
|
|
152
|
+
|
|
153
|
+
### Links
|
|
154
|
+
|
|
155
|
+
While developing `sharkey` I bumped into several dead-ends.
|
|
156
|
+
These are the people/links who helped me a lot:
|
|
157
|
+
|
|
158
|
+
* [Sinatra Recipes: DataMapper](http://recipes.sinatrarb.com/p/models/data_mapper?#article)
|
|
159
|
+
* [Sam Stern: Making a Simple, Database-Driven website with Sinatra and Heroku](http://samuelstern.wordpress.com/2012/11/28/making-a-simple-database-driven-website-with-sinatra-and-heroku/)
|
|
160
|
+
* [wooptoot: File Upload with Sinatra](http://www.wooptoot.com/file-upload-with-sinatra)
|
|
161
|
+
* [Random Ruby Thoughts: Upload Files in Sinatra](http://alfuken.tumblr.com/post/874428235/upload-and-download-files-in-sinatra)
|
|
162
|
+
* [The Bastards Book of Ruby: Parsing HTML with Nokogiri](http://ruby.bastardsbook.com/chapters/html-parsing/)
|
|
163
|
+
* [Unheap: showcase of lots and lots of Javascript stuff](http://www.unheap.com/)
|
|
164
|
+
|
|
165
|
+
### Notes
|
|
166
|
+
|
|
167
|
+
* When running this under development (with `rake preview`) the requests
|
|
168
|
+
seems _mother-fricking slow_; that's because of `shotgun` and it's tendency
|
|
169
|
+
to restart the application on each request.
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
The whole code is released under the *MIT-license*.
|
|
174
|
+
|
|
175
|
+
Check file `LICENSE.md` for details on what you can and
|
|
176
|
+
cannot do with it.
|
|
177
|
+
|
|
178
|
+
[delicious]: https://delicious.com/
|
|
179
|
+
[hoard]: http://www.urbandictionary.com/define.php?term=Tab-Hoarder
|
|
180
|
+
[gem]: http://link
|
|
181
|
+
[live]: http://saruman.link/sharkey/
|
|
182
|
+
[sinatra]: http://
|
|
183
|
+
[ruby]:
|
|
184
|
+
[vegas]:
|
|
185
|
+
[bootstrap]:
|
|
186
|
+
[bootswatch]:
|
|
187
|
+
[jquery]:
|
|
188
|
+
|
data/Rakefile
ADDED
data/bin/sharkey-web
ADDED
data/config.ru
ADDED
data/lib/sharkey.rb
ADDED
data/lib/sharkey/app.rb
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
# Application file for Sharkey
|
|
2
|
+
#
|
|
3
|
+
# Here we define the actions that Sinatra needs to take (routes)
|
|
4
|
+
|
|
5
|
+
require 'sinatra'
|
|
6
|
+
require 'slim'
|
|
7
|
+
require 'data_mapper'
|
|
8
|
+
|
|
9
|
+
# For making date/time user-friendly
|
|
10
|
+
require 'date'
|
|
11
|
+
require 'chronic_duration'
|
|
12
|
+
|
|
13
|
+
# To import Sharkey::Links and get page's Titles
|
|
14
|
+
require 'nokogiri'
|
|
15
|
+
require 'metainspector'
|
|
16
|
+
|
|
17
|
+
# Create and initialize the databases
|
|
18
|
+
require 'sharkey/models'
|
|
19
|
+
|
|
20
|
+
require 'sharkey/setting'
|
|
21
|
+
require 'sharkey/importerexporter'
|
|
22
|
+
|
|
23
|
+
require 'json'
|
|
24
|
+
|
|
25
|
+
module Sharkey
|
|
26
|
+
class App < Sinatra::Application
|
|
27
|
+
|
|
28
|
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
29
|
+
# Initializing the server
|
|
30
|
+
|
|
31
|
+
# Global in-app settings
|
|
32
|
+
# (not related to Sinatra per se)
|
|
33
|
+
Sharkey::Setting.initialize
|
|
34
|
+
|
|
35
|
+
# Helper functions that are accessible inside
|
|
36
|
+
# every place
|
|
37
|
+
helpers do
|
|
38
|
+
def delete_link id
|
|
39
|
+
link = Sharkey::Link.get id
|
|
40
|
+
return if not link
|
|
41
|
+
|
|
42
|
+
# Before deleting the link, we must remove
|
|
43
|
+
# all Sharkey::Tag associations
|
|
44
|
+
link.taggings.destroy if link.taggings
|
|
45
|
+
link.categorization.destroy if link.categorization
|
|
46
|
+
|
|
47
|
+
link.reload
|
|
48
|
+
link.destroy
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def delete_tag id
|
|
52
|
+
tag = Sharkey::Tag.get id
|
|
53
|
+
return if not tag
|
|
54
|
+
|
|
55
|
+
tag.taggings.destroy if tag.taggings
|
|
56
|
+
|
|
57
|
+
tag.reload
|
|
58
|
+
tag.destroy
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def delete_category id
|
|
62
|
+
category = Sharkey::Category.get id
|
|
63
|
+
return if not category
|
|
64
|
+
|
|
65
|
+
# * `category.parent` is a pointer to
|
|
66
|
+
# another Category
|
|
67
|
+
# * `category.categoryParent` is a pointer
|
|
68
|
+
# to the RELATIONSHIP to another Category
|
|
69
|
+
|
|
70
|
+
# Removing ties to other Categories...
|
|
71
|
+
if category.parent
|
|
72
|
+
category.parent.remove_child category
|
|
73
|
+
end
|
|
74
|
+
if category.categoryParent
|
|
75
|
+
category.categoryParent.destroy
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
if not category.childs.empty?
|
|
79
|
+
category.childs.each { |child| category.remove_child(child) }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# ...and to other Links...
|
|
83
|
+
if category.categorizations
|
|
84
|
+
category.categorizations.destroy
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# ...and finally to itself
|
|
88
|
+
category.reload
|
|
89
|
+
category.destroy
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def random_sentence
|
|
93
|
+
@sentences ||= File.readlines(File.join(File.dirname(__FILE__), '/public/data/sentences.txt'))
|
|
94
|
+
@sentences.sample
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Converts `datetime` object into a relative date String
|
|
98
|
+
# (like "2 days, 8 hours, 15 minutes, 2 seconds")
|
|
99
|
+
def relative_date datetime
|
|
100
|
+
return '' if datetime.nil?
|
|
101
|
+
|
|
102
|
+
# Now we'll calculate a short, human-friendly
|
|
103
|
+
# version of the full date (like '2 days, 5 hours')
|
|
104
|
+
added_secs = datetime.strftime('%s').to_i
|
|
105
|
+
now_secs = DateTime.now.strftime('%s').to_i
|
|
106
|
+
|
|
107
|
+
ChronicDuration.output(now_secs - added_secs)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Converts `datetime` object into a formatted date String
|
|
111
|
+
# suited for HTML5's <time datetime=""> attributes!
|
|
112
|
+
def formatted_date datetime
|
|
113
|
+
return '' if datetime.nil?
|
|
114
|
+
|
|
115
|
+
datetime.strftime '%Y-%m-%d %H:%m'
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
120
|
+
# Standalone pages
|
|
121
|
+
|
|
122
|
+
get '/' do
|
|
123
|
+
slim(:dashboard_index,
|
|
124
|
+
:layout => :dashboard,
|
|
125
|
+
:locals => { page: "home" })
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
get '/settings' do
|
|
129
|
+
slim(:settings_index,
|
|
130
|
+
:layout => :settings,
|
|
131
|
+
:locals => { page: "settings" })
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
135
|
+
# Deleting stuff
|
|
136
|
+
|
|
137
|
+
delete '/link/:id' do
|
|
138
|
+
delete_link(params[:id])
|
|
139
|
+
|
|
140
|
+
# If this is an AJAX request, we don't need
|
|
141
|
+
# to redirect anywhere!
|
|
142
|
+
# The JavaScript is responsible for updating
|
|
143
|
+
# the page, not us!
|
|
144
|
+
redirect back unless request.xhr?
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Extra parameters:
|
|
148
|
+
#
|
|
149
|
+
# - destroy_links If should also destroy links with this tag
|
|
150
|
+
#
|
|
151
|
+
delete '/tag/:id' do
|
|
152
|
+
|
|
153
|
+
# Welp, here we go!
|
|
154
|
+
# Send a DELETE request for each link
|
|
155
|
+
if (params[:destroy_links])
|
|
156
|
+
links = Sharkey::Link.by_tag(params[:id])
|
|
157
|
+
|
|
158
|
+
links.each { |link| delete_link link.id }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
delete_tag params[:id]
|
|
162
|
+
|
|
163
|
+
# If this is an AJAX request, we don't need
|
|
164
|
+
# to redirect anywhere!
|
|
165
|
+
# The JavaScript is responsible for updating
|
|
166
|
+
# the page, not us!
|
|
167
|
+
redirect back unless request.xhr?
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Extra parameters:
|
|
171
|
+
#
|
|
172
|
+
# - destroy_links If should also destroy links with this category
|
|
173
|
+
#
|
|
174
|
+
delete '/category/:id' do
|
|
175
|
+
|
|
176
|
+
# Welp, here we go!
|
|
177
|
+
# Send a DELETE request for each link
|
|
178
|
+
if (params[:destroy_links] == 'true')
|
|
179
|
+
links = Sharkey::Link.by_category(params[:id])
|
|
180
|
+
|
|
181
|
+
links.each { |link| delete_link link.id }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
delete_category params[:id]
|
|
185
|
+
|
|
186
|
+
# If this is an AJAX request, we don't need
|
|
187
|
+
# to redirect anywhere!
|
|
188
|
+
# The JavaScript is responsible for updating
|
|
189
|
+
# the page, not us!
|
|
190
|
+
redirect back unless request.xhr?
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
194
|
+
# Deleting too much stuff (Caution!)
|
|
195
|
+
|
|
196
|
+
delete '/all-links' do
|
|
197
|
+
Sharkey::Tagging.destroy
|
|
198
|
+
Sharkey::Link.destroy
|
|
199
|
+
redirect to '/'
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
delete '/all-tags' do
|
|
203
|
+
Sharkey::Tagging.destroy
|
|
204
|
+
Sharkey::Tag.destroy
|
|
205
|
+
redirect to '/'
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
delete '/all-categories' do
|
|
209
|
+
Sharkey::Categorization.destroy
|
|
210
|
+
Sharkey::CategoryParent.destroy
|
|
211
|
+
Sharkey::CategoryChild.destroy
|
|
212
|
+
Sharkey::Category.destroy
|
|
213
|
+
redirect to '/'
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
delete '/everything' do
|
|
217
|
+
# No need to be cautious
|
|
218
|
+
FileUtils.rm_f Sharkey::DATABASE_FILE
|
|
219
|
+
redirect to '/'
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
223
|
+
# Individual pages
|
|
224
|
+
get '/link/:id' do
|
|
225
|
+
the_link = Sharkey::Link.get(params[:id])
|
|
226
|
+
redirect to '/' if not the_link
|
|
227
|
+
|
|
228
|
+
slim(:link,
|
|
229
|
+
:layout => :dashboard,
|
|
230
|
+
locals: { page: "link", link: the_link })
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
get '/tag/:id' do
|
|
234
|
+
the_tag = Sharkey::Tag.get(params[:id])
|
|
235
|
+
redirect to '/' if not the_tag
|
|
236
|
+
|
|
237
|
+
slim(:tag,
|
|
238
|
+
:layout => :dashboard,
|
|
239
|
+
locals: { page: "tag", tag: the_tag })
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
get '/category/:id' do
|
|
243
|
+
the_category = Sharkey::Category.get(params[:id])
|
|
244
|
+
redirect to '/' if not the_category
|
|
245
|
+
|
|
246
|
+
slim(:category,
|
|
247
|
+
:layout => :dashboard,
|
|
248
|
+
locals: { page: "category", category: the_category })
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
252
|
+
# Creating things
|
|
253
|
+
|
|
254
|
+
post '/link' do
|
|
255
|
+
Sharkey::Link.create_link(params[:title],
|
|
256
|
+
params[:url],
|
|
257
|
+
params[:added_at],
|
|
258
|
+
params[:tags],
|
|
259
|
+
params[:category],
|
|
260
|
+
params[:comment])
|
|
261
|
+
|
|
262
|
+
# If AJAX request, don't redirect anywhere!
|
|
263
|
+
redirect back unless request.xhr?
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
post '/links' do
|
|
267
|
+
|
|
268
|
+
params[:url].split.each do |url|
|
|
269
|
+
|
|
270
|
+
Sharkey::Link.create_link(nil,
|
|
271
|
+
url,
|
|
272
|
+
nil,
|
|
273
|
+
params[:tags] || [],
|
|
274
|
+
params[:category],
|
|
275
|
+
"")
|
|
276
|
+
end
|
|
277
|
+
redirect back unless request.xhr?
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
post '/category' do
|
|
281
|
+
new_category = Sharkey::Category.first_or_create(name: params[:name]);
|
|
282
|
+
|
|
283
|
+
parent_category = Sharkey::Category.get(params[:parent])
|
|
284
|
+
# Silently fail if invalid ID was given
|
|
285
|
+
if (parent_category)
|
|
286
|
+
parent_category.add_child(new_category)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# If this is an AJAX request, we'll return a JSON
|
|
290
|
+
# string with information on the recently-added Category
|
|
291
|
+
if request.xhr?
|
|
292
|
+
return "{ \"id\": \"#{new_category.id}\", \"name\": \"#{new_category.name}\" }"
|
|
293
|
+
else
|
|
294
|
+
redirect back
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
299
|
+
# Updating things
|
|
300
|
+
#
|
|
301
|
+
# Unfortunately I couldn't use the PUT method - even when
|
|
302
|
+
# adding a hidden '<input>' and all that crap.
|
|
303
|
+
# It ain't the order of Sinatra routes also... So I fell
|
|
304
|
+
# back to good old POST with different URL.
|
|
305
|
+
|
|
306
|
+
post '/update/link/:id' do
|
|
307
|
+
return 404 if not params[:id]
|
|
308
|
+
|
|
309
|
+
Sharkey::Link.update_link(params[:id],
|
|
310
|
+
params[:title],
|
|
311
|
+
params[:url],
|
|
312
|
+
params[:tags],
|
|
313
|
+
params[:category],
|
|
314
|
+
params[:comment])
|
|
315
|
+
|
|
316
|
+
# If AJAX request, don't redirect anywhere!
|
|
317
|
+
redirect back unless request.xhr?
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
321
|
+
# Pages that list things
|
|
322
|
+
|
|
323
|
+
get '/links' do
|
|
324
|
+
# Creating an instance variable
|
|
325
|
+
# (visible inside all Views)
|
|
326
|
+
@links = Sharkey::Link.all
|
|
327
|
+
|
|
328
|
+
slim(:links,
|
|
329
|
+
:layout => :dashboard,
|
|
330
|
+
:locals => { page: "links" })
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
get '/tags' do
|
|
334
|
+
# Creating an instance variable
|
|
335
|
+
# (visible inside all Views)
|
|
336
|
+
@tags = Sharkey::Tag.all
|
|
337
|
+
|
|
338
|
+
# MagicSuggest, the jQuery plugin, uses this to give
|
|
339
|
+
# suggestions on Tag input fields.
|
|
340
|
+
#
|
|
341
|
+
# If request is AJAX, return a JSON
|
|
342
|
+
# array with all existing tags
|
|
343
|
+
if request.xhr?
|
|
344
|
+
return @tags.sort.to_json
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Now, the user can send other values to
|
|
348
|
+
# specify how they will get sorted
|
|
349
|
+
case params[:sort]
|
|
350
|
+
when 'name'
|
|
351
|
+
@tags = @tags.sort_by { |t| t.name }
|
|
352
|
+
|
|
353
|
+
when 'count'
|
|
354
|
+
@tags = @tags.sort_by { |t| t.taggings.count } .reverse
|
|
355
|
+
|
|
356
|
+
when 'id'
|
|
357
|
+
@tags = @tags.sort_by { |t| t.id }
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
slim(:tags,
|
|
361
|
+
:layout => :dashboard,
|
|
362
|
+
:locals => { page: "tags" })
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
get '/categories' do
|
|
366
|
+
|
|
367
|
+
# Let's start by showing all Categories
|
|
368
|
+
# WITHOUT parents.
|
|
369
|
+
# Then, recursively show their children
|
|
370
|
+
@categories = Sharkey::Category.orphans
|
|
371
|
+
|
|
372
|
+
# Now, the user can send other values to
|
|
373
|
+
# specify how they will get sorted
|
|
374
|
+
case params[:sort]
|
|
375
|
+
when 'name'
|
|
376
|
+
@categories = @categories.sort_by { |t| t.name }
|
|
377
|
+
|
|
378
|
+
when 'count'
|
|
379
|
+
@categories = @categories.sort_by { |t| t.categorizations.count } .reverse
|
|
380
|
+
|
|
381
|
+
when 'id'
|
|
382
|
+
@categories = @categories.sort_by { |t| t.id }
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
slim(:categories,
|
|
386
|
+
:layout => :dashboard,
|
|
387
|
+
:locals => { page: "categories" })
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
get '/favorites' do
|
|
391
|
+
@links = Sharkey::Link.all(favorite: true)
|
|
392
|
+
|
|
393
|
+
slim(:links,
|
|
394
|
+
:layout => :dashboard,
|
|
395
|
+
:locals => { page: "favorites" })
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
399
|
+
# Misc. pages
|
|
400
|
+
|
|
401
|
+
post '/debug' do
|
|
402
|
+
slim("#{params.inspect}")
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
post '/setting' do
|
|
406
|
+
# HACK
|
|
407
|
+
# BIG EXCEPTION are <select> elements, who fuck up
|
|
408
|
+
# the entire standard
|
|
409
|
+
if (params[:theme])
|
|
410
|
+
Sharkey::Setting['theme'] = params[:theme]
|
|
411
|
+
Sharkey::Setting.save
|
|
412
|
+
redirect back
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# Error for non-existing setting
|
|
416
|
+
return 500 unless Sharkey::Setting[params[:name]]
|
|
417
|
+
return 500 unless params[:value]
|
|
418
|
+
|
|
419
|
+
Sharkey::Setting[params[:name]] = params[:value]
|
|
420
|
+
Sharkey::Setting.save
|
|
421
|
+
|
|
422
|
+
if request.xhr?
|
|
423
|
+
return 200
|
|
424
|
+
else
|
|
425
|
+
redirect back
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# Import Sharkey::Links from Bookmark HTML files
|
|
430
|
+
# (eg. Firefox, Delicious, etc)
|
|
431
|
+
#
|
|
432
|
+
# POST requests automatically creates a temporary
|
|
433
|
+
# file for us.
|
|
434
|
+
#
|
|
435
|
+
post '/import' do
|
|
436
|
+
|
|
437
|
+
# If we got no temporary file from POST let's just
|
|
438
|
+
# ignore this request
|
|
439
|
+
unless (params[:file] and params[:file][:tempfile])
|
|
440
|
+
redirect back
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
ImporterExporter::import params['file'][:tempfile]
|
|
444
|
+
redirect to '/'
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
get '/tagcloud' do
|
|
448
|
+
@tags = Sharkey::Tag.all
|
|
449
|
+
|
|
450
|
+
slim(:tagcloud,
|
|
451
|
+
:layout => :dashboard,
|
|
452
|
+
:locals => { page: "tagcloud" })
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
# Surprise me!
|
|
456
|
+
get '/random' do
|
|
457
|
+
the_link = Sharkey::Link.all.sample
|
|
458
|
+
|
|
459
|
+
slim(:link,
|
|
460
|
+
:layout => :dashboard,
|
|
461
|
+
locals: { page: "link", link: the_link })
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# Toggles the favorite state of the Link with :id
|
|
465
|
+
#
|
|
466
|
+
# Returns the item's favorite state _after_ the change.
|
|
467
|
+
post '/favorite/:id' do
|
|
468
|
+
the_link = Sharkey::Link.get params[:id]
|
|
469
|
+
return 502 if not the_link
|
|
470
|
+
|
|
471
|
+
the_link.toggle_favorite
|
|
472
|
+
return "{ \"isFavorite\": #{the_link.favorite} }"
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
# Increase the visit count of a link
|
|
476
|
+
post '/visit/:id' do
|
|
477
|
+
the_link = Sharkey::Link.get params[:id]
|
|
478
|
+
return 502 if not the_link
|
|
479
|
+
|
|
480
|
+
the_link.visit
|
|
481
|
+
return "{ \"visitCount\": #{the_link.visit_count} }"
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
get '/help' do
|
|
485
|
+
slim(:help,
|
|
486
|
+
:layout => :centered,
|
|
487
|
+
locals: { page: "help" })
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
get '/about' do
|
|
491
|
+
slim(:about,
|
|
492
|
+
:layout => :centered,
|
|
493
|
+
locals: { page: "about" })
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# AJAX requested metadata from an URL
|
|
497
|
+
# Let's return, beyb
|
|
498
|
+
get '/metadata' do
|
|
499
|
+
return 404 if not params[:url]
|
|
500
|
+
|
|
501
|
+
begin
|
|
502
|
+
page = MetaInspector.new(params[:url], :allow_redirections => :all)
|
|
503
|
+
|
|
504
|
+
hash = {
|
|
505
|
+
'pageTitle' => "#{page.title}",
|
|
506
|
+
'pageDescription' => "#{page.description}"
|
|
507
|
+
}
|
|
508
|
+
return hash.to_json
|
|
509
|
+
|
|
510
|
+
rescue
|
|
511
|
+
# This error-handling sucks, perhaps I should
|
|
512
|
+
# return error values according to what actually
|
|
513
|
+
# happened on the server
|
|
514
|
+
return 503
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
519
|
+
# Error-handling
|
|
520
|
+
|
|
521
|
+
not_found do
|
|
522
|
+
slim(:'404', :locals => { url: request.fullpath })
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
|