diff --git a/README.md b/README.md index de40bbd..8b4a898 100644 --- a/README.md +++ b/README.md @@ -31,15 +31,14 @@ To Do: - [x] Auth errors - [x] Session set - [x] Logout -- [ ] Card Area +- [x] Card Area - [x] List cards - [x] Add cards - [x] Edit card - - [ ] Delete card + - [x] Delete card - Card Memorization - - [ ] Card type toggle - - [ ] Show card - - [ ] Flip card - - [ ] Next card - - [ ] Last card - - [ ] Mark as known + - [x] Card type toggle + - [x] Flip card + - [x] Mark as known, next card + - [x] Next card + - [x] Card filters diff --git a/flash_cards.py b/flash_cards.py index 04837ff..d219898 100644 --- a/flash_cards.py +++ b/flash_cards.py @@ -57,7 +57,10 @@ def close_db(error): @app.route('/') def index(): - return render_template('index.html') + if session.get('logged_in'): + return redirect(url_for('general')) + else: + return redirect(url_for('login')) @app.route('/cards') @@ -65,9 +68,39 @@ def cards(): if not session.get('logged_in'): return redirect(url_for('login')) db = get_db() - cur = db.execute('SELECT id, type, front, back, known FROM cards ORDER BY id DESC') + query = ''' + SELECT id, type, front, back, known + FROM cards + ORDER BY id DESC + ''' + cur = db.execute(query) cards = cur.fetchall() - return render_template('cards.html', cards=cards) + return render_template('cards.html', cards=cards, filter_name=None) + + +@app.route('/filter_cards/') +def filter_cards(filter_name): + if not session.get('logged_in'): + return redirect(url_for('login')) + + filters = { + "all": "where 1 = 1", + "general": "where type = 1", + "code": "where type = 2", + "known": "where known = 1", + "unknown": "where known = 0", + } + + query = filters.get(filter_name) + + if not query: + return redirect(url_for('cards')) + + db = get_db() + fullquery = "SELECT id, type, front, back, known FROM cards " + query + " ORDER BY id DESC" + cur = db.execute(fullquery) + cards = cur.fetchall() + return render_template('cards.html', cards=cards, filter_name=filter_name) @app.route('/add', methods=['POST']) @@ -90,7 +123,12 @@ def edit(card_id): if not session.get('logged_in'): return redirect(url_for('login')) db = get_db() - cur = db.execute('SELECT * FROM cards WHERE id = ?', [card_id]) + query = ''' + SELECT id, type, front, back, known + FROM cards + WHERE id = ? + ''' + cur = db.execute(query, [card_id]) card = cur.fetchone() return render_template('edit.html', card=card) @@ -102,7 +140,16 @@ def edit_card(): selected = request.form.getlist('known') known = bool(selected) db = get_db() - db.execute('UPDATE cards set type = ?, front = ?, back = ?, known = ? where id = ?', + command = ''' + UPDATE cards + SET + type = ?, + front = ?, + back = ?, + known = ? + WHERE id = ? + ''' + db.execute(command, [request.form['type'], request.form['front'], request.form['back'], @@ -129,14 +176,62 @@ def delete(card_id): def general(): if not session.get('logged_in'): return redirect(url_for('login')) - return render_template('general.html') + return memorize("general") @app.route('/code') def code(): if not session.get('logged_in'): return redirect(url_for('login')) - return render_template('code.html') + return memorize("code") + + +def memorize(card_type): + if card_type == "general": + type = 1 + elif card_type == "code": + type = 2 + else: + return redirect(url_for('cards')) + + card = get_card(type) + if not card: + flash("You've learned all the " + card_type + " cards.") + return redirect(url_for('cards')) + short_answer = (len(card['back']) < 75) + return render_template('memorize.html', + card=card, + card_type=card_type, + short_answer=short_answer) + + +def get_card(type): + db = get_db() + + query = ''' + SELECT + id, type, front, back, known + FROM cards + WHERE + type = ? + and known = 0 + ORDER BY RANDOM() + LIMIT 1 + ''' + + cur = db.execute(query, [type]) + return cur.fetchone() + + +@app.route('/mark_known//') +def mark_known(card_id, card_type): + if not session.get('logged_in'): + return redirect(url_for('login')) + db = get_db() + db.execute('UPDATE cards SET known = 1 WHERE id = ?', [card_id]) + db.commit() + flash('Card marked as known.') + return redirect(url_for(card_type)) @app.route('/login', methods=['GET', 'POST']) diff --git a/static/fastclick.min.js b/static/fastclick.min.js new file mode 100644 index 0000000..cf06427 --- /dev/null +++ b/static/fastclick.min.js @@ -0,0 +1 @@ +!function(){"use strict";function t(e,o){function i(t,e){return function(){return t.apply(e,arguments)}}var r;if(o=o||{},this.trackingClick=!1,this.trackingClickStart=0,this.targetElement=null,this.touchStartX=0,this.touchStartY=0,this.lastTouchIdentifier=0,this.touchBoundary=o.touchBoundary||10,this.layer=e,this.tapDelay=o.tapDelay||200,this.tapTimeout=o.tapTimeout||700,!t.notNeeded(e)){for(var a=["onMouse","onClick","onTouchStart","onTouchMove","onTouchEnd","onTouchCancel"],c=this,s=0,u=a.length;u>s;s++)c[a[s]]=i(c[a[s]],c);n&&(e.addEventListener("mouseover",this.onMouse,!0),e.addEventListener("mousedown",this.onMouse,!0),e.addEventListener("mouseup",this.onMouse,!0)),e.addEventListener("click",this.onClick,!0),e.addEventListener("touchstart",this.onTouchStart,!1),e.addEventListener("touchmove",this.onTouchMove,!1),e.addEventListener("touchend",this.onTouchEnd,!1),e.addEventListener("touchcancel",this.onTouchCancel,!1),Event.prototype.stopImmediatePropagation||(e.removeEventListener=function(t,n,o){var i=Node.prototype.removeEventListener;"click"===t?i.call(e,t,n.hijacked||n,o):i.call(e,t,n,o)},e.addEventListener=function(t,n,o){var i=Node.prototype.addEventListener;"click"===t?i.call(e,t,n.hijacked||(n.hijacked=function(t){t.propagationStopped||n(t)}),o):i.call(e,t,n,o)}),"function"==typeof e.onclick&&(r=e.onclick,e.addEventListener("click",function(t){r(t)},!1),e.onclick=null)}}var e=navigator.userAgent.indexOf("Windows Phone")>=0,n=navigator.userAgent.indexOf("Android")>0&&!e,o=/iP(ad|hone|od)/.test(navigator.userAgent)&&!e,i=o&&/OS 4_\d(_\d)?/.test(navigator.userAgent),r=o&&/OS [6-7]_\d/.test(navigator.userAgent),a=navigator.userAgent.indexOf("BB10")>0;t.prototype.needsClick=function(t){switch(t.nodeName.toLowerCase()){case"button":case"select":case"textarea":if(t.disabled)return!0;break;case"input":if(o&&"file"===t.type||t.disabled)return!0;break;case"label":case"iframe":case"video":return!0}return/\bneedsclick\b/.test(t.className)},t.prototype.needsFocus=function(t){switch(t.nodeName.toLowerCase()){case"textarea":return!0;case"select":return!n;case"input":switch(t.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1}return!t.disabled&&!t.readOnly;default:return/\bneedsfocus\b/.test(t.className)}},t.prototype.sendClick=function(t,e){var n,o;document.activeElement&&document.activeElement!==t&&document.activeElement.blur(),o=e.changedTouches[0],n=document.createEvent("MouseEvents"),n.initMouseEvent(this.determineEventType(t),!0,!0,window,1,o.screenX,o.screenY,o.clientX,o.clientY,!1,!1,!1,!1,0,null),n.forwardedTouchEvent=!0,t.dispatchEvent(n)},t.prototype.determineEventType=function(t){return n&&"select"===t.tagName.toLowerCase()?"mousedown":"click"},t.prototype.focus=function(t){var e;o&&t.setSelectionRange&&0!==t.type.indexOf("date")&&"time"!==t.type&&"month"!==t.type?(e=t.value.length,t.setSelectionRange(e,e)):t.focus()},t.prototype.updateScrollParent=function(t){var e,n;if(e=t.fastClickScrollParent,!e||!e.contains(t)){n=t;do{if(n.scrollHeight>n.offsetHeight){e=n,t.fastClickScrollParent=n;break}n=n.parentElement}while(n)}e&&(e.fastClickLastScrollTop=e.scrollTop)},t.prototype.getTargetElementFromEventTarget=function(t){return t.nodeType===Node.TEXT_NODE?t.parentNode:t},t.prototype.onTouchStart=function(t){var e,n,r;if(t.targetTouches.length>1)return!0;if(e=this.getTargetElementFromEventTarget(t.target),n=t.targetTouches[0],o){if(r=window.getSelection(),r.rangeCount&&!r.isCollapsed)return!0;if(!i){if(n.identifier&&n.identifier===this.lastTouchIdentifier)return t.preventDefault(),!1;this.lastTouchIdentifier=n.identifier,this.updateScrollParent(e)}}return this.trackingClick=!0,this.trackingClickStart=t.timeStamp,this.targetElement=e,this.touchStartX=n.pageX,this.touchStartY=n.pageY,t.timeStamp-this.lastClickTimen||Math.abs(e.pageY-this.touchStartY)>n?!0:!1},t.prototype.onTouchMove=function(t){return this.trackingClick?((this.targetElement!==this.getTargetElementFromEventTarget(t.target)||this.touchHasMoved(t))&&(this.trackingClick=!1,this.targetElement=null),!0):!0},t.prototype.findControl=function(t){return void 0!==t.control?t.control:t.htmlFor?document.getElementById(t.htmlFor):t.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")},t.prototype.onTouchEnd=function(t){var e,a,c,s,u,l=this.targetElement;if(!this.trackingClick)return!0;if(t.timeStamp-this.lastClickTimethis.tapTimeout)return!0;if(this.cancelNextClick=!1,this.lastClickTime=t.timeStamp,a=this.trackingClickStart,this.trackingClick=!1,this.trackingClickStart=0,r&&(u=t.changedTouches[0],l=document.elementFromPoint(u.pageX-window.pageXOffset,u.pageY-window.pageYOffset)||l,l.fastClickScrollParent=this.targetElement.fastClickScrollParent),c=l.tagName.toLowerCase(),"label"===c){if(e=this.findControl(l)){if(this.focus(l),n)return!1;l=e}}else if(this.needsFocus(l))return t.timeStamp-a>100||o&&window.top!==window&&"input"===c?(this.targetElement=null,!1):(this.focus(l),this.sendClick(l,t),o&&"select"===c||(this.targetElement=null,t.preventDefault()),!1);return o&&!i&&(s=l.fastClickScrollParent,s&&s.fastClickLastScrollTop!==s.scrollTop)?!0:(this.needsClick(l)||(t.preventDefault(),this.sendClick(l,t)),!1)},t.prototype.onTouchCancel=function(){this.trackingClick=!1,this.targetElement=null},t.prototype.onMouse=function(t){return this.targetElement?t.forwardedTouchEvent?!0:t.cancelable&&(!this.needsClick(this.targetElement)||this.cancelNextClick)?(t.stopImmediatePropagation?t.stopImmediatePropagation():t.propagationStopped=!0,t.stopPropagation(),t.preventDefault(),!1):!0:!0},t.prototype.onClick=function(t){var e;return this.trackingClick?(this.targetElement=null,this.trackingClick=!1,!0):"submit"===t.target.type&&0===t.detail?!0:(e=this.onMouse(t),e||(this.targetElement=null),e)},t.prototype.destroy=function(){var t=this.layer;n&&(t.removeEventListener("mouseover",this.onMouse,!0),t.removeEventListener("mousedown",this.onMouse,!0),t.removeEventListener("mouseup",this.onMouse,!0)),t.removeEventListener("click",this.onClick,!0),t.removeEventListener("touchstart",this.onTouchStart,!1),t.removeEventListener("touchmove",this.onTouchMove,!1),t.removeEventListener("touchend",this.onTouchEnd,!1),t.removeEventListener("touchcancel",this.onTouchCancel,!1)},t.notNeeded=function(t){var e,o,i,r;if("undefined"==typeof window.ontouchstart)return!0;if(o=+(/Chrome\/([0-9]+)/.exec(navigator.userAgent)||[,0])[1]){if(!n)return!0;if(e=document.querySelector("meta[name=viewport]")){if(-1!==e.content.indexOf("user-scalable=no"))return!0;if(o>31&&document.documentElement.scrollWidth<=window.outerWidth)return!0}}if(a&&(i=navigator.userAgent.match(/Version\/([0-9]*)\.([0-9]*)/),i[1]>=10&&i[2]>=3&&(e=document.querySelector("meta[name=viewport]")))){if(-1!==e.content.indexOf("user-scalable=no"))return!0;if(document.documentElement.scrollWidth<=window.outerWidth)return!0}return"none"===t.style.msTouchAction||"manipulation"===t.style.touchAction?!0:(r=+(/Firefox\/([0-9]+)/.exec(navigator.userAgent)||[,0])[1],r>=27&&(e=document.querySelector("meta[name=viewport]"),e&&(-1!==e.content.indexOf("user-scalable=no")||document.documentElement.scrollWidth<=window.outerWidth))?!0:"none"===t.style.touchAction||"manipulation"===t.style.touchAction?!0:!1)},t.attach=function(e,n){return new t(e,n)},"function"==typeof define&&"object"==typeof define.amd&&define.amd?define(function(){return t}):"undefined"!=typeof module&&module.exports?(module.exports=t.attach,module.exports.FastClick=t):window.FastClick=t}(); \ No newline at end of file diff --git a/static/general.js b/static/general.js new file mode 100644 index 0000000..c3f47cf --- /dev/null +++ b/static/general.js @@ -0,0 +1,57 @@ +$(document).ready(function(){ + if ($('.memorizePanel').length != 0) { + + $('.flipCard').click(function(){ + $('.cardFront').hide(); + $('.cardBack').show(); + }); + } + + if ($('.cardForm').length != 0) { + + $('.cardForm').submit(function(){ + + var frontTrim = $.trim($('#front').val()); + $('#front').val(frontTrim); + var backTrim = $.trim($('#back').val()); + $('#back').val(backTrim); + + if (! $('#front').val() || ! $('#back').val()) { + return false; + } + }); + } + + if ($('.editPanel').length != 0) { + + function checkit() { + var checkedVal = $('input[name=type]:checked').val(); + if (checkedVal === undefined) { + // hide the fields + $('.fieldFront').hide(); + $('.fieldBack').hide(); + $('.saveButton').hide(); + } else { + $('.toggleButton').removeClass('toggleSelected'); + $(this).addClass('toggleSelected'); + + if (checkedVal == '1') { + $('textarea[name=back]').attr('rows', 5); + } else { + $('textarea[name=back]').attr('rows', 12); + } + + $('.fieldFront').show(); + $('.fieldBack').show(); + $('.saveButton').show(); + } + } + + $('.toggleButton').click(checkit); + + checkit(); + } + + // to remove the short delay on click on touch devices + FastClick.attach(document.body); +}); diff --git a/static/style.css b/static/style.css index 655c2c7..7750666 100644 --- a/static/style.css +++ b/static/style.css @@ -5,4 +5,46 @@ textarea { font-family: monospace; +} + +.cardContent h4 { + margin-top: 0; +} + +.alignContainer { + display: table; + width: 100%; +} + +.alignMiddle { + height: 20em; + display: table-cell; + vertical-align: middle; +} + +.cardBack { + display: none; +} + +.largerText { + font-size: 2em; +} + + +/* disables collapsing header menu */ + +.navbar-collapse.collapse { + display: block!important; +} + +.navbar-nav>li, .navbar-nav { + float: left !important; +} + +.navbar-nav.navbar-right:last-child { + margin-right: 0 !important; +} + +.navbar-right { + float: right!important; } \ No newline at end of file diff --git a/templates/cards.html b/templates/cards.html index 3d8ca99..18095be 100644 --- a/templates/cards.html +++ b/templates/cards.html @@ -1,9 +1,9 @@ {% extends "layout.html" %} {% block body %} -
+

Add a Card

-
+
- +
@@ -31,57 +31,46 @@
+ - {% for card in cards %} -
-

- - {{ card.front }} -

- {% if card.type == 1 %} - {{ card.back|replace("\n", "
")|safe }} - {% else %} -
{{ card.back|safe }}
- {% endif %} -
-
- {% else %} -
  • Unbelievable. No cards here so far. - {% endfor %} +
    +
    - {% endblock %} diff --git a/templates/code.html b/templates/code.html deleted file mode 100644 index 58d5dce..0000000 --- a/templates/code.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "layout.html" %} -{% block body %} - code -{% endblock %} \ No newline at end of file diff --git a/templates/edit.html b/templates/edit.html index e8221eb..d18f7a8 100644 --- a/templates/edit.html +++ b/templates/edit.html @@ -2,7 +2,7 @@ {% block body %}

    Edit Card #{{ card.id }}

    - +
    - +
    diff --git a/templates/general.html b/templates/general.html deleted file mode 100644 index 3838823..0000000 --- a/templates/general.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "layout.html" %} -{% block body %} - general -{% endblock %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index e9b21a3..0000000 --- a/templates/index.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "layout.html" %} -{% block body %} - -{% endblock %} diff --git a/templates/layout.html b/templates/layout.html index 29b05a0..8d6cf57 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -2,48 +2,34 @@ CS Flash Cards - - - - - + +
    -
    +
    - {% for message in get_flashed_messages() %} {% endfor %} @@ -51,6 +37,11 @@
    + + + + diff --git a/templates/login.html b/templates/login.html index b10b8e9..3388b74 100644 --- a/templates/login.html +++ b/templates/login.html @@ -2,7 +2,8 @@ {% block body %}

    Login

    {% if error %} - {% endif %} + + {% endif %}
    diff --git a/templates/memorize.html b/templates/memorize.html new file mode 100644 index 0000000..52077fd --- /dev/null +++ b/templates/memorize.html @@ -0,0 +1,91 @@ +{% extends "layout.html" %} +{% block body %} + +
    +
    +
    + General + Code +
    +
    +
    + +
    + +
    + +
    +
    +
    +
    +
    +

    {{ card.front }}

    +
    +
    +
    +
    +
    +
    +
    +
    + {% if card.type == 1 %} + {% if short_answer %} +
    + {% endif %} + {{ card.back|replace("\n", "
    ")|safe }} + {% if short_answer %} +
    + {% endif %} + {% else %} +
    {{ card.back|safe }}
    + {% endif %} +
    +
    +
    +
    +
    + +
    + + + +{% endblock %} \ No newline at end of file