MobileBlur

Changes On Branch b2d6f6e2e967f76a
Login

Changes On Branch b2d6f6e2e967f76a

Changes In Branch feature/pretty Through [b2d6f6e2e9] Excluding Merge-Ins

This is equivalent to a diff from e23a2d0023 to b2d6f6e2e9

2011-11-22
23:16
[9678271609e046a71f06dd091c7ae666716cc670] Added a Google Analytics tracking code check-in: 07a2f861bb user: spiffy tags: develop
05:47
Story list page is styled much more nicely check-in: c5a2ec2308 user: spiffy tags: better-story-list, feature/pretty
05:22
Added some color. It doesn't look very good, but it's at least not atrocious anymore. check-in: b2d6f6e2e9 user: spiffy tags: feature/pretty
2011-11-21
03:15
Create new branch named "feature/pretty" check-in: e8b2e22e32 user: spiffy tags: feature/pretty
03:08
[2bd69fdb5b4c5fc04d40ea402cc18510b073c4e3] Mobileblur is now the default web2py app check-in: e23a2d0023 user: spiffy tags: develop
2011-11-20
23:52
[12e10e324e61e687d024bb1aac1a1c031ef48dd3] Added each story's timestamp below it in the story list check-in: 4e2808f7d1 user: spiffy tags: develop

Modified applications/mobileblur/controllers/default.py from [667cfb0951] to [cdfea9e32e].

22
23
24
25
26
27
28

29
30
31
32
33
34
35
        try:
            results = newsblur.login(login_form.vars["username"], login_form.vars["password"])
            response.cookies["nb_cookie"] = newsblur.cookies["newsblur_sessionid"]
            response.cookies["nb_cookie"]["path"] = "/"
            redirect(URL("index"))
        except Exception as ex:
            login_form.insert(-1, ex.message)


    return dict(login_form=login_form)


def logout():
    response.cookies["nb_cookie"] = ""
    response.cookies["nb_cookie"]["expires"] = -10







>







22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
        try:
            results = newsblur.login(login_form.vars["username"], login_form.vars["password"])
            response.cookies["nb_cookie"] = newsblur.cookies["newsblur_sessionid"]
            response.cookies["nb_cookie"]["path"] = "/"
            redirect(URL("index"))
        except Exception as ex:
            login_form.insert(-1, ex.message)
            login_form._class = "alert-message block-message error"

    return dict(login_form=login_form)


def logout():
    response.cookies["nb_cookie"] = ""
    response.cookies["nb_cookie"]["expires"] = -10

Modified applications/mobileblur/controllers/feeds.py from [353169fb7a] to [1863aef141].

1
2
3

4
5


6







7



8


9

10
11
12
13
14
# -*- coding: utf-8 -*-

from pprint import pprint


def view():


    stories = newsblur.feed(request.args[0])["stories"]







    feeds = newsblur.feeds(flat=True)["feeds"]



    feed = [feed for feed in feeds.itervalues() if feed["id"]==int(request.args[0])][0]


    return dict(stories=stories, feed=feed)


def mark_read():
    if len(request.args) > 0:
        newsblur.mark_feed_as_read(request.args[0])
    redirect(URL("default", "index"))



>


>
>
|
>
>
>
>
>
>
>
|
>
>
>
|
>
>

>





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# -*- coding: utf-8 -*-

from pprint import pprint
import time

def view():
    print ""
    s = time.time()
    feed = newsblur.feed(request.args[0])
    stories = feed["stories"]
    print time.time() - s

    print feed.keys()

    if not feed.has_key("feed_title"):
        s = time.time()
        feeds = newsblur.feeds(flat=True)["feeds"]
        print time.time() - s

        s = time.time()
        feed = [feed for feed in feeds.itervalues() if feed["id"]==int(request.args[0])][0]
        print time.time() - s

    return dict(stories=stories, feed=feed)


def mark_read():
    if len(request.args) > 0:
        newsblur.mark_feed_as_read(request.args[0])
    redirect(URL("default", "index"))

Modified applications/mobileblur/static/css/base.css from [b086dc4442] to [037cce1332].

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- always indent the first line and add space below paragraphs
- bullets and numbers style and indent
- form and table padding
- code blocks
- left and right padding to quoted text
- page layout alignment, width and padding (change this for spaces)
- column widths (change this to use left_sidebar and right_sidebar)
- backrgound images and colors (change this for colors)
- web2py specific (.flash, .error)

Notice:
- even if you use a different layout/css you may need classes .flash and .error
- this is all color neutral except for #349C01 (header, links, lines)
- there are two backrgound images: images/background.png and images/header.png

License: This file is released under BSD and MIT

*/

/*     
  credit is left where credit is due.







|





|







23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- always indent the first line and add space below paragraphs
- bullets and numbers style and indent
- form and table padding
- code blocks
- left and right padding to quoted text
- page layout alignment, width and padding (change this for spaces)
- column widths (change this to use left_sidebar and right_sidebar)
- background images and colors (change this for colors)
- web2py specific (.flash, .error)

Notice:
- even if you use a different layout/css you may need classes .flash and .error
- this is all color neutral except for #349C01 (header, links, lines)
- there are two background images: images/background.png and images/header.png

License: This file is released under BSD and MIT

*/

/*     
  credit is left where credit is due.
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
h1 {font-size: 2.0em;}
h2 {font-size: 1.8em;}
h3 {font-size: 1.4em;}
h4 {font-size: 1.2em;}
h5 {font-size: 1.0em;}
h6 {font-size: 0.8em;}

/*********** page layout alignment, width and padding ***********/
/*body {background-color: #000;}*/
#container, #header, #page, #content, #statusbar,
#footer, #wrapper { display:block; line-height: 170%; }
#wrapper {width: 900px;}
#container {
    margin: 0 auto;
    padding: 0;
}
#wrapper {margin: 0 auto;} 
#wrapper {background-color: #fff; padding: 5px;} 
#statusbar { margin: 5px 0px 20px 0px;}
#footer {    
    margin-top: 30px;    
    padding: 5px;    
}
#statusbar, #footer {    
    background: #eaeaea; 
    border-top: 1px #aaa solid;    
}
#logo {
    width: 68px;
    height: 62px;
    background: url(../images/logo.png);
}
#appname {
    color: #cccccc;
}

#right_sidebar { width: 160px; float:right; display: none; }
#left_sidebar { width: 160px; float:left; display: none; }
#content { float: left; /*width: 740px;*//*width: 63%;*/ /*width: 640px; float:left;*/ } /* uncomment this if you are going to use sidebars */
 
.auth_navbar {
   top: 0px;
   float: right;
   padding: 3px 10px 3px 10px; 
}

/*********** web2py specific ***********/
div.flash {
    font-weight: bold;
    display: none;
    position: fixed;    
    padding: 10px;
    top: 40px;







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







340
341
342
343
344
345
346







































347
348
349
350
351
352
353
h1 {font-size: 2.0em;}
h2 {font-size: 1.8em;}
h3 {font-size: 1.4em;}
h4 {font-size: 1.2em;}
h5 {font-size: 1.0em;}
h6 {font-size: 0.8em;}








































/*********** web2py specific ***********/
div.flash {
    font-weight: bold;
    display: none;
    position: fixed;    
    padding: 10px;
    top: 40px;
551
552
553
554
555
556
557











558












559
560
561
562

563








  thead { display: table-header-group; } /* css-discuss.incutio.com/wiki/Printing_Tables */ 
  tr, img { page-break-inside: avoid; }
  @page { margin: 0.5cm; }
  p, h2, h3 { orphans: 3; widows: 3; }
  h2, h3{ page-break-after: avoid; }
}












#story-list > .read > h2 {












    color: #607890;
}
#story-list > .unread > h2 {
    color: #306;

}















>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
|

|
|
>

>
>
>
>
>
>
>
>
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
  thead { display: table-header-group; } /* css-discuss.incutio.com/wiki/Printing_Tables */ 
  tr, img { page-break-inside: avoid; }
  @page { margin: 0.5cm; }
  p, h2, h3 { orphans: 3; widows: 3; }
  h2, h3{ page-break-after: avoid; }
}

body {
    margin: 0 2% 0 2%;
    background-color: #CC6600;
}

header {
    background-color: #cc0000;
    border-bottom: 1px solid black;
    color: white;
}

#story-list {
}

#story-list > .story > a {
    text-decoration: none;
    border-radius: 5px;
    font-size: 2em;
    font-weight: bold;
    background-color: #CC9933;
    padding: 0 6px 0 6px;
}

#story-list > .story > .read {
    color: #CC3333;
}
#story-list > .story > .unread {
    color: #CC0000;
/*    color: #A8A8EE;*/
}

#story-list > .story {
    background-color: white;
    padding: .25%;
    border: 1px solid black;
    border-radius: 5px;
    margin-top: .33%;
}

Modified applications/mobileblur/views/default/index.html from [1ac89d52bc] to [cb0da83eed].

1
2
3
4
5
6
7
8
9
10
11
12
{{left_sidebar_enabled=right_sidebar_enabled=False}}
{{extend 'layout.html'}}

{{ for feed in feeds.itervalues(): }}
    {{ if threshold == -1 and feed["ng"] > 0: }} <span class='ng'>[ {{= feed["ng"] }} ]</span> {{ pass }}
    {{ if threshold <= 0 and feed["nt"] > 0: }} <span class='nt'>[ {{= feed["nt"] }} ]</span> {{ pass }}
    {{if feed["ps"] > 0: }}<span class='ps'>[ {{= feed["ps"] }} ]</span> {{ pass }}
    <a href="{{= URL(c="feeds", f="view", args=[feed["id"]]) }}">{{= feed["feed_title"] }}</a><br />
{{ pass }}

{{block left_sidebar}}New Left Sidebar Content{{end}}
{{block right_sidebar}}New Right Sidebar Content{{end}}
<








<
<
<

1
2
3
4
5
6
7
8




{{extend 'layout.html'}}

{{ for feed in feeds.itervalues(): }}
    {{ if threshold == -1 and feed["ng"] > 0: }} <span class='ng'>[ {{= feed["ng"] }} ]</span> {{ pass }}
    {{ if threshold <= 0 and feed["nt"] > 0: }} <span class='nt'>[ {{= feed["nt"] }} ]</span> {{ pass }}
    {{if feed["ps"] > 0: }}<span class='ps'>[ {{= feed["ps"] }} ]</span> {{ pass }}
    <a href="{{= URL(c="feeds", f="view", args=[feed["id"]]) }}">{{= feed["feed_title"] }}</a><br />
{{ pass }}



Modified applications/mobileblur/views/feeds/view.html from [f19fa72882] to [a3c96a9b2b].

1
2
3

4
5

6
7




8

9


10

11

12
13
14
15
{{left_sidebar_enabled=right_sidebar_enabled=False}}
{{extend 'layout.html'}}


<h1>{{= feed["feed_title"] }}</h1>
<a href="{{= URL("mark_read", args=[feed["id"]]) }}">Mark feed as read</a>


<section id="story-list">




    {{ for story in stories: }}

        <a href="{{= URL(c="stories", f="view", vars=dict(story=story["id"], feed_id=feed["id"])) }}" class="{{= "unread" if story["read_status"] == 1 else "read" }}"><h2>{{= story["story_title"] }}</h2></a>


        <p>{{= story["story_date"] }}</p>

    {{ pass }}

</section>

{{block left_sidebar}}New Left Sidebar Content{{end}}
{{block right_sidebar}}New Right Sidebar Content{{end}}
<


>
|
|
>


>
>
>
>

>
|
>
>
|
>

>

<
<
<

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22




{{extend 'layout.html'}}

{{ block header_bonus }}
    <h2>{{= feed["feed_title"] }}</h2>
    <a href="{{= URL("mark_read", args=[feed["id"]]) }}" class="button">Mark feed as read</a>
{{ end }}

<section id="story-list">
{{
import time 
s = time.time()
}}
    {{ for story in stories: }}
        <div class="story">
            <a href="{{= URL(c="stories", f="view", vars=dict(story=story["id"], feed_id=feed["id"])) }}" class="{{= "unread" if story["read_status"] == 1 else "read" }}">
                {{= story["story_title"] }}
            </a>
            <p>{{= story["story_date"] }}</p>
        </div>
    {{ pass }}
{{ print time.time() - s }}
</section>



Modified applications/mobileblur/views/layout.html from [eb19032d37] to [4a91494d8a].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="{{=T.accepted_language or 'en'}}" class="no-js"><!-- no-js need it for modernzr -->
  <head>
    
    <meta charset="utf-8" />	  	
    <!-- www.phpied.com/conditional-comments-block-downloads/ -->
    <!--[if IE]><![endif]-->    
    <!-- Always force latest IE rendering engine 
	 (even in intranet) & Chrome Frame 
	 Remove this if you use the .htaccess -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    
    <title>{{=response.title or request.application}}</title>
    
    <!-- http://dev.w3.org/html5/markup/meta.name.html -->
    <meta name="application-name" content="{{=request.application}}" />	
    
    <!-- Speaking of Google, don't forget to set your site up: 
	 http://google.com/webmasters -->
    <meta name="google-site-verification" content="" />
    
    <!--  Mobile Viewport Fix
	  j.mp/mobileviewport & davidbcalhoun.com/2010/viewport-metatag 
	  device-width: Occupy full width of the screen in its current orientation
	  initial-scale = 1.0 retains dimensions instead of zooming out if page height > device height
	  maximum-scale = 1.0 retains dimensions instead of zooming in if page width < device width
      -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
    
    <!-- Place favicon.ico and apple-touch-icon.png in the root of your domain and delete these references -->
    <link rel="shortcut icon" href="{{=URL('static','favicon.ico')}}" type="image/x-icon">
    <link rel="apple-touch-icon" href="{{=URL('static','favicon.png')}}">

    <!-- For the less-enabled mobile browsers like Opera Mini -->
    <link rel="stylesheet" media="handheld" href="{{=URL('static','css/handheld.css')}}">












|














|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="{{=T.accepted_language or 'en'}}" class="no-js"><!-- no-js need it for modernzr -->
  <head>
    
    <meta charset="utf-8" />	  	
    <!-- www.phpied.com/conditional-comments-block-downloads/ -->
    <!--[if IE]><![endif]-->    
    <!-- Always force latest IE rendering engine 
	 (even in intranet) & Chrome Frame 
	 Remove this if you use the .htaccess -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    
    <title>{{ block title }}{{=response.title or request.application}}{{ end }}</title>
    
    <!-- http://dev.w3.org/html5/markup/meta.name.html -->
    <meta name="application-name" content="{{=request.application}}" />	
    
    <!-- Speaking of Google, don't forget to set your site up: 
	 http://google.com/webmasters -->
    <meta name="google-site-verification" content="" />
    
    <!--  Mobile Viewport Fix
	  j.mp/mobileviewport & davidbcalhoun.com/2010/viewport-metatag 
	  device-width: Occupy full width of the screen in its current orientation
	  initial-scale = 1.0 retains dimensions instead of zooming out if page height > device height
	  maximum-scale = 1.0 retains dimensions instead of zooming in if page width < device width
      -->
    <meta name="viewport" content="width=device-width, initial-scale=2, maximum-scale=5.0">
    
    <!-- Place favicon.ico and apple-touch-icon.png in the root of your domain and delete these references -->
    <link rel="shortcut icon" href="{{=URL('static','favicon.ico')}}" type="image/x-icon">
    <link rel="apple-touch-icon" href="{{=URL('static','favicon.png')}}">

    <!-- For the less-enabled mobile browsers like Opera Mini -->
    <link rel="stylesheet" media="handheld" href="{{=URL('static','css/handheld.css')}}">
55
56
57
58
59
60
61

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
    else: width_content='100%'
    if left_sidebar_enabled: left_sidebar_style = 'style="display: block;"'
    else: left_sidebar_style = 'style="display: none;"'
    if right_sidebar_enabled: right_sidebar_style = 'style="display: block;"'
    else: right_sidebar_style = 'style="display: none;"'
    style_content = 'style="width: %s"' % width_content
    }}

  </head>
  
  <!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ -->	
  <!--[if lt IE 7 ]> <body class="ie6"> <![endif]-->
  <!--[if IE 7 ]>    <body class="ie7"> <![endif]-->
  <!--[if IE 8 ]>    <body class="ie8"> <![endif]-->
  <!--[if IE 9 ]>    <body class="ie9"> <![endif]-->
  <!--[if (gt IE 9)|!(IE)]><!--> <body> <!--<![endif]-->
    
    <div class="flash">{{=response.flash or ''}}</div> <!-- notification div -->
    
    <div id="container">	      		      	
      
      <div id="wrapper">		
	
	<div id="header"> <!-- header and login nav -->
	  {{block header}} <!-- this is default header -->
	  {{try:}}{{=auth.navbar(action=URL('default','user'))}}{{except:pass}}		          
	  <h1><span id="appname">{{=request.application.capitalize()}}</span>App</h1>
	  <div style="clear: both;"></div><!-- Clear the divs -->
	  {{end}}				  					  
	</div><!-- header  -->
	
	<div id="statusbar"><!-- statusbar is menu zone -->
	  {{block statusbar}} <!-- this is default statusbar -->
	  {{#------ superfish menu ------}}
	  {{=MENU(response.menu,_class='sf-menu')}}
	  <script type="text/javascript">
	    jQuery(document).ready(function(){
	    jQuery('ul.sf-menu').superfish();});
	  </script>
	  <div style="clear: both;"></div><!-- Clear the divs -->	
	  {{end}}				
	</div><!-- statusbar -->
	
	<div id="page"> <!-- Here my central body -->	  

	  {{if left_sidebar_enabled:}}
          <div id="left_sidebar" {{=XML(left_sidebar_style)}} >
            <div style="padding: 4px;">
	      {{block left_sidebar}}Content Left Sidebar{{end}}		  
            </div>
          </div><!-- left_sidebar -->
	  {{pass}}

      <a href="{{= URL("default", "logout") }}">Log out</a>
	  <!-- content -->
	  <div id="content" {{=XML(style_content)}} >
	    {{include}}	
	  </div>
	  <!-- content -->

	  {{if right_sidebar_enabled:}}
          <div id="right_sidebar" {{=XML(right_sidebar_style)}} >
            <div style="padding: 4px;">
              {{block right_sidebar}}Content Right Sidebar{{end}}
            </div>
	  </div><!-- right_sidebar -->
          {{pass}}


	  <div style="clear: both;"></div><!-- Clear the divs -->
	  
	</div><!-- page -->							
	
	<div id="footer">
	  {{block footer}} <!-- this is default footer -->
	  <a href="http://www.web2py.com/" style="float: left; padding-right: 6px;">
	    <img src="{{=URL('static','images/poweredby.png')}}"/>
	  </a>
	  {{=T('Copyright')}} &#169; 2010				
	  <div style="clear: both;"></div><!-- Clear the divs -->
	  {{end}}

	</div><!-- footer -->			
      </div><!-- wrapper -->
    </div><!-- container -->		
    
    <!--[if lt IE 7 ]>
	<script src="{{=URL('static','js/dd_belatedpng.js')}}"></script>
	<script> DD_belatedPNG.fix('img, .png_bg'); //fix any <img> or .png_bg background-images </script>
	<![endif]-->
    
    <!-- asynchronous google analytics: mathiasbynens.be/notes/async-analytics-snippet 







>











<
|
<
<
<

<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
|
<
|
<
|
<
<
<
<
<
<
<
|
<
<
|
|
<
<
|
<
<
<
<
<
<
<

<
<
<
<
<
|
<
<
<
<
<
<
<
|
|
<
<







55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

74



75





76









77

78

79







80


81
82


83







84





85







86
87


88
89
90
91
92
93
94
    else: width_content='100%'
    if left_sidebar_enabled: left_sidebar_style = 'style="display: block;"'
    else: left_sidebar_style = 'style="display: none;"'
    if right_sidebar_enabled: right_sidebar_style = 'style="display: block;"'
    else: right_sidebar_style = 'style="display: none;"'
    style_content = 'style="width: %s"' % width_content
    }}
<!--    <link rel="stylesheet" href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css">-->
  </head>
  
  <!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ -->	
  <!--[if lt IE 7 ]> <body class="ie6"> <![endif]-->
  <!--[if IE 7 ]>    <body class="ie7"> <![endif]-->
  <!--[if IE 8 ]>    <body class="ie8"> <![endif]-->
  <!--[if IE 9 ]>    <body class="ie9"> <![endif]-->
  <!--[if (gt IE 9)|!(IE)]><!--> <body> <!--<![endif]-->
    
    <div class="flash">{{=response.flash or ''}}</div> <!-- notification div -->
    

	<header>



	  {{block header}} <!-- this is default header -->





          <h1><a href="{{= URL("mobileblur", "default", "index") }}">MobileBlur</a></h1>









	  {{end}}

      {{ block header_bonus }}{{end}}

	</header>







	


    <section id="content" {{=XML(style_content)}} >
        {{include}}	


    </section>













    <footer>







        <a href="{{= URL("default", "logout") }}">Log out</a>
    </footer>


    
    <!--[if lt IE 7 ]>
	<script src="{{=URL('static','js/dd_belatedpng.js')}}"></script>
	<script> DD_belatedPNG.fix('img, .png_bg'); //fix any <img> or .png_bg background-images </script>
	<![endif]-->
    
    <!-- asynchronous google analytics: mathiasbynens.be/notes/async-analytics-snippet 

Modified applications/mobileblur/views/stories/view.html from [c0c8c655df] to [63977d234b].

1
2
3
4


5


6
7
8
9
10
{{left_sidebar_enabled=right_sidebar_enabled=False}}
{{extend 'layout.html'}}

<a href="{{= story["story_permalink"] }}"><h1>{{= story["story_title"] }}</h1></a>


<a href="{{= URL("mark_read", vars=dict(story_id=story["id"], feed_id=feed_id)) }}">Mark story as read</a>



{{= XML(story["story_content"]) }}

{{block left_sidebar}}New Left Sidebar Content{{end}}
{{block right_sidebar}}New Right Sidebar Content{{end}}
<


|
>
>
|
>
>


<
<
<

1
2
3
4
5
6
7
8
9
10




{{extend 'layout.html'}}

<a href="{{= story["story_permalink"] }}">
    <h2>{{= story["story_title"] }}</h2>
</a>
<a href="{{= URL("mark_read", vars=dict(story_id=story["id"], feed_id=feed_id)) }}" class="button">
    Mark story as read
</a>

{{= XML(story["story_content"]) }}