├── storyweb ├── analysis │ ├── __init__.py │ ├── html.py │ ├── text.py │ ├── calais.py │ └── extract.py ├── migrate │ ├── alembic.ini │ ├── versions │ │ ├── 353dff346d3_initial_migration.py │ │ └── 176bef6a90a3_basic_schema.py │ ├── script.py.mako │ └── env.py ├── static │ ├── img │ │ ├── edit.png │ │ └── grano.png │ ├── templates │ │ ├── card_icon.html │ │ ├── card_item.html │ │ ├── reference.html │ │ ├── pager.html │ │ ├── card_new.html │ │ ├── article_list.html │ │ ├── search.html │ │ ├── link_new.html │ │ ├── card.html │ │ └── link.html │ ├── js │ │ ├── directives │ │ │ ├── card_icon.js │ │ │ ├── reference.js │ │ │ ├── card_item.js │ │ │ ├── pager.js │ │ │ ├── new_link.js │ │ │ ├── editor.js │ │ │ └── link.js │ │ ├── controllers │ │ │ ├── article_list.js │ │ │ ├── search.js │ │ │ ├── card_new.js │ │ │ ├── app.js │ │ │ └── card.js │ │ └── app.js │ └── style │ │ ├── medium.less │ │ ├── loader.less │ │ └── app.less ├── __init__.py ├── authz.py ├── model │ ├── __init__.py │ ├── forms.py │ ├── spider_tag.py │ ├── util.py │ ├── user.py │ ├── reference.py │ ├── link.py │ └── card.py ├── upgrade.py ├── spiders │ ├── wiki.py │ ├── __init__.py │ ├── util.py │ ├── openduka.py │ └── opencorp.py ├── manage.py ├── search │ ├── queries.py │ ├── __init__.py │ ├── result_proxy.py │ └── mapping.py ├── views │ ├── ui.py │ ├── auth.py │ ├── __init__.py │ ├── references_api.py │ ├── cards_api.py │ ├── links_api.py │ └── admin.py ├── default_settings.py ├── assets.py ├── queue.py ├── templates │ ├── login.html │ ├── app.html │ └── layout.html ├── core.py └── util.py ├── .bowerrc ├── Procfile ├── MANIFEST.in ├── requirements.txt ├── settings.py.tmpl ├── fabric_deploy ├── supervisor.template ├── nginx.template └── fabfile.py ├── setup.py ├── bower.json ├── LICENSE ├── .gitignore ├── app.json └── README.md /storyweb/analysis/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "storyweb/static/vendor" 3 | } 4 | -------------------------------------------------------------------------------- /storyweb/migrate/alembic.ini: -------------------------------------------------------------------------------- 1 | [alembic] 2 | script_location=. 3 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn --preload --log-file - storyweb.manage:app 2 | worker: celery -A storyweb.queue worker 3 | -------------------------------------------------------------------------------- /storyweb/static/img/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pudo-attic/storyweb/HEAD/storyweb/static/img/edit.png -------------------------------------------------------------------------------- /storyweb/static/img/grano.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pudo-attic/storyweb/HEAD/storyweb/static/img/grano.png -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE requirements.txt Procfile README.md 2 | recursive-include storyweb/migrate * 3 | global-exclude *.pyc 4 | -------------------------------------------------------------------------------- /storyweb/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | # shut up useless SA warning: 3 | import warnings 4 | warnings.filterwarnings('ignore', 5 | 'Unicode type received non-unicode bind param value.') 6 | from sqlalchemy.exc import SAWarning 7 | warnings.filterwarnings('ignore', category=SAWarning) 8 | -------------------------------------------------------------------------------- /storyweb/authz.py: -------------------------------------------------------------------------------- 1 | from werkzeug.exceptions import Forbidden 2 | from flask.ext.login import current_user 3 | 4 | 5 | def logged_in(): 6 | return current_user.is_authenticated() 7 | 8 | 9 | def require(pred): 10 | if not pred: 11 | raise Forbidden("Sorry, you're not permitted to do this!") 12 | -------------------------------------------------------------------------------- /storyweb/static/templates/card_icon.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /storyweb/static/templates/card_item.html: -------------------------------------------------------------------------------- 1 |
{{card.summary}}
8 |9 | {{relDate()}} by 10 | {{card.author.display_name}} 11 |
12 |%s
' % text 19 | except wikipedia.WikipediaException, pe: 20 | log.exception(pe) 21 | -------------------------------------------------------------------------------- /storyweb/manage.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from flask.ext.script import Manager 3 | from flask.ext.assets import ManageAssets 4 | from flask.ext.migrate import MigrateCommand 5 | 6 | from storyweb.views import app, assets 7 | from storyweb.upgrade import upgrade as upgrade_ 8 | 9 | 10 | log = logging.getLogger(__name__) 11 | manager = Manager(app) 12 | manager.add_command("assets", ManageAssets(assets)) 13 | manager.add_command('db', MigrateCommand) 14 | 15 | 16 | @manager.command 17 | def upgrade(): 18 | """ Upgrade the database and re-index search. """ 19 | upgrade_() 20 | 21 | 22 | if __name__ == "__main__": 23 | manager.run() 24 | -------------------------------------------------------------------------------- /storyweb/static/js/controllers/search.js: -------------------------------------------------------------------------------- 1 | storyweb.controller('SearchCtrl', ['$scope', '$location', '$http', 'cfpLoadingBar', 2 | function($scope, $location, $http, cfpLoadingBar) { 3 | 4 | $scope.search = $location.search(); 5 | $scope.result = {'results': []}; 6 | 7 | $scope.loadPage = function(page) { 8 | $location.search('offset', $scope.result.limit * (page-1)); 9 | }; 10 | 11 | $http.get('/api/1/cards/_search', {'params': $scope.search}).then(function(res) { 12 | $scope.result = res.data; 13 | $scope.result.end = Math.min($scope.result.total, $scope.result.offset + $scope.result.limit); 14 | }); 15 | }]); 16 | -------------------------------------------------------------------------------- /fabric_deploy/supervisor.template: -------------------------------------------------------------------------------- 1 | [program:%(server-name)s-web] 2 | environment=STORYWEB_SETTINGS='%(deploy-dir)ssettings.py' 3 | directory=%(project-dir)s 4 | command=%(ve-dir)s/bin/gunicorn --log-file - -b %(host)s:%(port)s storyweb.manage:app -w 5 5 | user=%(user)s 6 | stdout_logfile=%(gunicorn-log)s 7 | stderr_logfile=%(gunicorn-err-log)s 8 | stopsignal=QUIT 9 | 10 | [program:%(server-name)s-worker] 11 | environment=STORYWEB_SETTINGS='%(deploy-dir)ssettings.py' 12 | directory=%(project-dir)s 13 | command=%(ve-dir)s/bin/celery -A storyweb.queue worker 14 | user=%(user)s 15 | stdout_logfile=%(celery-log)s 16 | stderr_logfile=%(celery-err-log)s 17 | stopsignal=QUIT 18 | -------------------------------------------------------------------------------- /storyweb/analysis/text.py: -------------------------------------------------------------------------------- 1 | import re 2 | from unicodedata import normalize as ucnorm, category 3 | 4 | REMOVE_SPACES = re.compile(r'\s+') 5 | 6 | 7 | def normalize(text): 8 | if not isinstance(text, unicode): 9 | text = unicode(text) 10 | chars = [] 11 | for char in ucnorm('NFKD', text): 12 | cat = category(char)[0] 13 | if cat in ['C', 'Z', 'S']: 14 | chars.append(u' ') 15 | elif cat in ['M', 'P']: 16 | continue 17 | else: 18 | chars.append(char) 19 | text = u''.join(chars) 20 | text = REMOVE_SPACES.sub(' ', text) 21 | text = text.strip().lower() 22 | #return ucnorm('NFKC', text) 23 | return text 24 | -------------------------------------------------------------------------------- /storyweb/search/queries.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def cards_query(req): 4 | qstr = req.get('q', '').strip() 5 | if len(qstr): 6 | q = {'query_string': {'query': qstr}} 7 | bq = [ 8 | {"term": {"title": {"value": qstr, "boost": 10.0}}}, 9 | {"term": {"aliases": {"value": qstr, "boost": 6.0}}}, 10 | {"term": {"text": {"value": qstr, "boost": 3.0}}} 11 | ] 12 | q = { 13 | "bool": { 14 | "must": q, 15 | "should": bq 16 | } 17 | } 18 | else: 19 | q = {'match_all': {}} 20 | return { 21 | 'query': q, 22 | '_source': ['title', 'category', 'summary', 'id', 'updated_at', 'author'] 23 | } 24 | -------------------------------------------------------------------------------- /storyweb/static/templates/article_list.html: -------------------------------------------------------------------------------- 1 |{{config.APP_DESCRIPTION}}
15 | {% if config.MOTD %} 16 |21 |
', '').replace('
', '') 35 | body = { 36 | 'status': exc.code, 37 | 'name': exc.name, 38 | 'message': message 39 | } 40 | headers = exc.get_headers(request.environ) 41 | else: 42 | body = { 43 | 'status': 500, 44 | 'name': exc.__class__.__name__, 45 | 'message': unicode(exc) 46 | } 47 | headers = {} 48 | return jsonify(body, status=body.get('status'), 49 | headers=headers) 50 | 51 | 52 | @app.errorhandler(Invalid) 53 | def handle_invalid(exc): 54 | body = { 55 | 'status': 400, 56 | 'name': 'Invalid Data', 57 | 'message': unicode(exc), 58 | 'errors': exc.asdict() 59 | } 60 | return jsonify(body, status=400) 61 | -------------------------------------------------------------------------------- /storyweb/static/js/directives/editor.js: -------------------------------------------------------------------------------- 1 | // this is derived from: 2 | // https://github.com/thijsw/angular-medium-editor 3 | 4 | storyweb.directive('mediumEditor', function() { 5 | return { 6 | require: 'ngModel', 7 | restrict: 'AE', 8 | scope: { 9 | }, 10 | link: function(scope, iElement, iAttrs, ctrl) { 11 | 12 | angular.element(iElement).addClass('angular-medium-editor'); 13 | 14 | // Parse options 15 | var placeholder = '', 16 | opts = { 17 | 'buttons': ["bold", "italic", "anchor", "header1", "header2", "quote", "orderedlist"], 18 | 'cleanPastedHTML': true 19 | }; 20 | 21 | var onChange = function() { 22 | scope.$apply(function() { 23 | 24 | // If user cleared the whole text, we have to reset the editor because MediumEditor 25 | // lacks an API method to alter placeholder after initialization 26 | if (iElement.html() === '
38 | No notes have been made
42 |