Merge pull request #37 from alphagov/templating-flow

Manage templates flow
This commit is contained in:
minglis
2016-01-06 09:45:01 +00:00
21 changed files with 363 additions and 149 deletions

View File

@@ -6,6 +6,8 @@ addons:
postgresql: '9.3'
env:
secure: jT9BIioqBMkOdLZhU+WJNdnRJ+06G7qUx4QqEVldp96dJwmWpPEvA0XbitdnQt/WXYkpMlDbgSApvvGj2ZNvdpowRRe5HFX8D2Udhi2g9+cXgKrQxH6zv0evJyQLOjCINW6KtgMCJ5wkYR3qQ4BQawlDt6ecpmeboKTmvs2W8jZ09aV4IKKvdd7BwFon10QVPF5ny10G83unLtKnKgRMjSSLnaEiA78pE/LSUkekK4mhmtl+yfQf60cIuQGcN9NCYIt5PrdYYyMkbUaht9ykwL2C11sp5JYPClI9k6lrlpGJCdL9wbJwejGhR/pEqwJ4tKK8Zv+mngmkbzE6fd5ehuRMnIUAifG4t3p6WbhKwY5pJsdVyPgWcRSPXOJA7yEcAeTAvWcC++6mCIFBeMxt/yQNw02jkFHeNKRh2twTRvr4xWZHq9FsVxTEVz89OOuue3IkkyDNmVusGJ9+AVRIn9Oa+U/r3bDnrs7jz+meSwb82GZUBzFpUe2pe8qeBE572Ay7yHB73VHUgp/2A1qkZ4SnTjTpMbnS5RdXTgwtMkOs5MLZgteCVxFL3sHcr9e/B3UIUnzKUSPXXOjHyDxBwrABWo81V9Vp2IPV7P9Ofv8zroudjQxK5MOcbmiPQF+eEB9L4DvkUBNsGxtJ/nmPp6tmN0Xjo0xXVdZCEVj29Og=
before_install:
- nvm install 5.0.0
install:
- npm install
- pip install -r requirements_for_test.txt

View File

@@ -0,0 +1,43 @@
(function(Modules) {
"use strict";
if (
!('oninput' in document.createElement('input'))
) return;
const tagPattern = /\(\([^\)\(]+\)\)/g;
Modules.HighlightTags = function() {
this.start = function(textarea) {
this.$textbox = $(textarea)
.wrap(`
<div class='textbox-highlight-wrapper' />
`)
.after(this.$backgroundMaskForeground = $(`
<div class="textbox-highlight-background" aria-hidden="true" />
<div class="textbox-highlight-mask" aria-hidden="true" />
<div class="textbox-highlight-foreground" aria-hidden="true" />
`))
.on("input", this.update)
.on("scroll", this.maintainScrollParity);
this.$textbox
.trigger("input");
};
this.update = () => this.$backgroundMaskForeground.html(
this.$textbox.val().replace(
tagPattern, match => `<span class='tag'>${match}</span>`
)
);
this.maintainScrollParity = () => this.$backgroundMaskForeground.scrollTop(
this.$textbox.scrollTop()
);
};
})(window.GOVUK.Modules);

View File

@@ -1,5 +1 @@
//= include ../../../bower_components/jquery/dist/jquery.js
(function() {
console.log("Hello world");
})();
$(() => GOVUK.modules.start());

View File

@@ -0,0 +1,83 @@
.textbox-highlight {
&-wrapper {
position: relative;
}
&-textbox {
resize: none;
z-index: 20;
background: none;
}
&-textbox,
&-background,
&-foreground,
&-mask {
@include core-19;
display: block;
box-sizing: border-box;
position: relative;
width: 500px;
height: 200px;
margin: 0;
padding: 4px;
overflow: hidden;
}
&-background,
&-foreground,
&-mask {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
color: transparent;
white-space: pre-wrap;
border: 2px solid transparent;
}
&-background {
z-index: 10;
.tag {
background: $light-blue;
border-radius: 3px;
color: transparent;
display: inline;
}
}
&-mask {
z-index: 30;
.tag {
color: $white;
text-shadow: 0 1px 0 $light-blue, 0 -1px 0 $light-blue;
position: relative;
display: inline;
overflow: hidden;
}
}
&-foreground {
z-index: 40;
.tag {
color: transparent;
border-radius: 3px;
overflow: hidden;
display: inline;
box-shadow: inset 0.75em 0 0 0 rgba($light-blue, .7),
inset -0.75em 0 0 0 rgba($light-blue, .7);
}
}
}

View File

@@ -38,6 +38,7 @@
@import 'components/navigation';
@import 'components/big-number';
@import 'components/banner';
@import 'components/textbox';
@import 'views/job';

View File

@@ -4,5 +4,5 @@ main = Blueprint('main', __name__)
from app.main.views import (
index, sign_in, register, two_factor, verify, sms, add_service, code_not_received, jobs, dashboard
index, sign_in, register, two_factor, verify, sms, add_service, code_not_received, jobs, dashboard, templates
)

View File

@@ -64,11 +64,6 @@ def apikeys():
return render_template('views/api-keys.html')
@main.route("/manage-templates")
def managetemplates():
return render_template('views/manage-templates.html')
@main.route("/edit-template")
def edittemplate():
return render_template('views/edit-template.html')
@main.route("/verification-not-received")
def verificationnotreceived():
return render_template('views/verification-not-received.html')

View File

@@ -0,0 +1,32 @@
from flask import request, render_template, redirect, url_for
from app.main import main
@main.route("/templates")
def manage_templates():
return render_template('views/manage-templates.html')
@main.route("/templates/template", methods=['GET', 'POST'])
def add_template():
if request.method == 'GET':
return render_template(
'views/edit-template.html',
template_name='Reminder',
template_body='Vehicle tax: Your vehicle tax for ((registration number)) expires on ((date)). Tax your vehicle at www.gov.uk/vehicle-tax', # noqa
h1='Edit template'
)
elif request.method == 'POST':
return redirect(url_for('.manage_templates'))
@main.route("/templates/template/add", methods=['GET', 'POST'])
def edit_template():
if request.method == 'GET':
return render_template(
'views/edit-template.html',
h1='Add template'
)
elif request.method == 'POST':
return redirect(url_for('.manage_templates'))

View File

@@ -47,5 +47,5 @@
{% endblock %}
{% block body_end %}
<script type="text/javascript" src="{{ asset_path }}javascripts/main.js" /></script>
<script type="text/javascript" src="{{ asset_path }}javascripts/all.js" /></script>
{% endblock %}

View File

@@ -1,11 +1,9 @@
{% macro submit_form(button_text, back_link) %}
{% macro submit_form(button_text, back_link=False) %}
<div class="submit-form">
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="submit" class="button" value="{{ button_text }}" />
{% if back_link %}
<a class="submit-form-back-link" role="button" href="{{ back_link }}">Back</a>
{% endif %}
</form>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="submit" class="button" value="{{ button_text }}" />
{% if back_link %}
<a class="submit-form-back-link" role="button" href="{{ back_link }}">Back</a>
{% endif %}
</div>
{% endmacro %}

View File

@@ -0,0 +1,15 @@
{% macro textbox(name, label, value='', small=True, highlight_tags=False) %}
<div class="form-group">
<label class="form-label" for="{{ name }}">{{ label }}</label>
{% if small %}
<input class="form-control" id="{{ name }}" name="{{ name }}" type="text" value="{{ value }}">
{% else %}
<textarea
class="form-control {% if highlight_tags %}textbox-highlight-textbox{% endif %}"
id="{{ name }}" name="{{ name }}"
cols="30" rows="10"
{% if highlight_tags %}data-module='highlight-tags'{% endif %}
>{{ value }}</textarea>
{% endif %}
</div>
{% endmacro %}

View File

@@ -15,50 +15,54 @@
<h2 class="heading-medium">Check and confirm</h2>
{{ submit_form(
"Send {} text messages".format(number_of_recipients),
url_for(".sendsms")
) }}
<form method="POST" enctype="multipart/form-data">
<h3 class="heading-small">First 3 messages</h2>
{{ submit_form(
"Send {} text messages".format(number_of_recipients),
url_for(".sendsms")
) }}
{% if recipients.first_three and recipients.last_three %}
<h3 class="heading-small">First 3 messages</h2>
{% for recipient in recipients.first_three %}
{{ sms_message(
message_template|replace_placeholders(recipient),
"{}".format(recipient['phone'])
) }}
{% endfor %}
{% if recipients.first_three and recipients.last_three %}
<h3 class="heading-small">Last 3 messages</h2>
{% for recipient in recipients.first_three %}
{{ sms_message(
message_template|replace_placeholders(recipient),
"{}".format(recipient['phone'])
) }}
{% endfor %}
{% for recipient in recipients.last_three %}
{{ sms_message(
message_template|replace_placeholders(recipient),
"{}".format(recipient['phone'])
) }}
{% endfor %}
<h3 class="heading-small">Last 3 messages</h2>
{% else %}
{% for recipient in recipients.last_three %}
{{ sms_message(
message_template|replace_placeholders(recipient),
"{}".format(recipient['phone'])
) }}
{% endfor %}
{% for recipient in recipients.all %}
{{ sms_message(
message_template|replace_placeholders(recipient),
"{}".format(recipient['phone'])
) }}
{% endfor %}
{% else %}
{% endif %}
{% for recipient in recipients.all %}
{{ sms_message(
message_template|replace_placeholders(recipient),
"{}".format(recipient['phone'])
) }}
{% endfor %}
<p>
<a href="#">See all</a>
</p>
{% endif %}
{{ submit_form(
"Send {} text messages".format(number_of_recipients),
url_for(".sendsms")
) }}
<p>
<a href="#">See all</a>
</p>
{{ submit_form(
"Send {} text messages".format(number_of_recipients),
url_for(".sendsms")
) }}
</form>

View File

@@ -1,4 +1,6 @@
{% extends "withnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/submit-form.html" import submit_form %}
{% block page_title %}
GOV.UK Notify | Edit template
@@ -6,13 +8,14 @@ GOV.UK Notify | Edit template
{% block maincolumn_content %}
<h1 class="heading-xlarge">{{ h1 }}</h1>
<h1 class="heading-xlarge">Edit template</h1>
<p>Here's where you can edit an exiting template (including delete) or add a new one</p>
<form method="post">
{{ textbox(name='template_name', label='Template name', value=template_name) }}
{{ textbox(name='template_body', label='Message', small=False, value=template_body, highlight_tags=True) }}
{{ submit_form('Save and continue') }}
</form>
<p><a href="manage-templates">Back to manage templates</a></p>
{% endblock %}

View File

@@ -13,7 +13,9 @@ GOV.UK Notify | Notifications activity
{{ uploaded_file_name }}
</h1>
<p>
{{ banner(flash_message) }}
</p>
<ul class="grid-row job-totals">
<li class="column-one-third">
@@ -36,7 +38,7 @@ GOV.UK Notify | Notifications activity
</ul>
<p>
Sent with template <a href="{{ url_for('.edittemplate') }}">{{ template_used }}</a> at {{ uploaded_file_time }}
Sent with template <a href="{{ url_for('.edit_template') }}">{{ template_used }}</a> at {{ uploaded_file_time }}
</p>
{% call(item) table(

View File

@@ -7,15 +7,17 @@ GOV.UK Notify | Manage templates
{% block maincolumn_content %}
<h1 class="heading-xlarge">Manage templates</h1>
<h1 class="heading-xlarge">Manage templates</h1>
<p>Here's where you can view templates, choose to add one, or edit/delete one.</p>
<p>Here's where you can view templates, choose to add one, or edit/delete one.</p>
<p><a href="edit-template">Here is my first template</a></p>
<p>
<a href="{{ url_for('.edit_template') }}">Here is my first template</a>
</p>
<p>
<a class="button" href="edit-template" role="button">Add a new message template</a>
</p>
<p>
<a class="button" href="{{ url_for('.edit_template') }}" role="button">Add a new message template</a>
</p>
{% endblock %}

View File

@@ -24,7 +24,7 @@
{% endfor %}
<p>
or <a href="{{ url_for(".managetemplates") }}">create a new template</a>
or <a href="{{ url_for(".add_template") }}">create a new template</a>
</p>
<h2 class="heading-medium">2. Add recipients</h2>

View File

@@ -2,7 +2,6 @@
"name": "GOV.UK Notify admin frontend",
"version": "0.0.1",
"dependencies": {
"jquery": "1.11.2",
"govuk_template": "https://github.com/alphagov/govuk_template/releases/download/v0.16.0/jinja_govuk_template-0.16.0.tgz"
}
}

89
gulpfile.babel.js Normal file
View File

@@ -0,0 +1,89 @@
// GULPFILE
// - - - - - - - - - - - - - - -
// This file processes all of the assets in the "src" folder
// and outputs the finished files in the "dist" folder.
// 1. LIBRARIES
// - - - - - - - - - - - - - - -
var gulp = require('gulp'),
plugins = require('gulp-load-plugins')(),
// 2. CONFIGURATION
// - - - - - - - - - - - - - - -
paths = {
src: 'app/assets/',
dist: 'app/static/',
templates: 'app/templates/'
};
// 3. TASKS
// - - - - - - - - - - - - - - -
// Move GOV.UK template resources
gulp.task('copy:govuk_template:template', () => gulp.src('bower_components/govuk_template/views/layouts/govuk_template.html')
.pipe(gulp.dest(paths.templates))
);
gulp.task('copy:govuk_template:assets', () => gulp.src('bower_components/govuk_template/assets/**/*')
.pipe(gulp.dest(paths.dist))
);
// Concatenate and minify
gulp.task('jquery', () => plugins.jquery.src({
release: 1,
flags: [
'-ajax', '-ajax/jsonp', '-ajax/load', '-ajax/parseJSON',
'-ajax/parseXML', '-ajax/script', '-ajax/var/nonce',
'-ajax/var/rquery', '-ajax/xhr', '-manipulation/_evalUrl',
'-deprecated', '-effects', '-effects/Tween',
'-effects/animatedSelector', '-effects/support', '-event-alias'
]
})
.pipe(gulp.dest(paths.dist + 'javascripts/'))
);
gulp.task('javascripts', () => gulp.src([
paths.src + 'govuk_frontend_toolkit/javascripts/govuk/modules.js',
paths.src + 'javascripts/highlightTags.js',
paths.src + 'javascripts/main.js'
])
.pipe(plugins.babel({
presets: ['es2015']
}))
.pipe(plugins.uglify())
.pipe(plugins.concat('all.js'))
.pipe(gulp.dest(paths.dist + 'javascripts/'))
);
gulp.task('sass', () => gulp.src(paths.src + '/stylesheets/main*.scss')
.pipe(plugins.sass({outputStyle: 'compressed'}))
.pipe(gulp.dest(paths.dist + '/stylesheets'))
);
// Copy images
gulp.task('images', () => gulp.src(paths.src + 'images/**/*')
.pipe(gulp.dest(paths.dist + '/images'))
);
// Watch for changes and re-run tasks
gulp.task('watchForChanges', function() {
gulp.watch(paths.src + 'javascripts/**/*', ['javascripts']);
gulp.watch(paths.src + 'stylesheets/**/*', ['sass']);
gulp.watch(paths.src + 'images/**/*', ['images']);
});
// Default: compile everything
gulp.task('default',
['copy:govuk_template:template', 'copy:govuk_template:assets', 'javascripts', 'sass', 'images']
);
// Optional: recompile on changes
gulp.task('watch',
['default', 'watchForChanges']
);

View File

@@ -1,77 +0,0 @@
// GULPFILE
// - - - - - - - - - - - - - - -
// This file processes all of the assets in the "src" folder
// and outputs the finished files in the "dist" folder.
// 1. LIBRARIES
// - - - - - - - - - - - - - - -
var gulp = require('gulp'),
plugins = require('gulp-load-plugins')(),
// 2. CONFIGURATION
// - - - - - - - - - - - - - - -
paths = {
src: 'app/assets/',
dist: 'app/static/',
templates: 'app/templates/'
};
// 3. TASKS
// - - - - - - - - - - - - - - -
// Move GOV.UK template resources
gulp.task('copy:govuk_template:template', function() {
return gulp.src('bower_components/govuk_template/views/layouts/govuk_template.html')
.pipe(gulp.dest(paths.templates));
});
gulp.task('copy:govuk_template:assets', function() {
return gulp.src('bower_components/govuk_template/assets/**/*')
.pipe(gulp.dest(paths.dist));
});
// Concatenate and minify
gulp.task('javascripts', function() {
return gulp.src(paths.src + 'javascripts/main.js')
.pipe(plugins.include())
.pipe(gulp.dest(paths.dist + 'javascripts/'))
.pipe(plugins.uglify())
.on('error', function(e) { console.log("Uglify did not complete."); })
.pipe(gulp.dest(paths.dist + 'javascripts/'));
});
gulp.task('sass', function () {
return gulp.src(paths.src + '/stylesheets/main*.scss')
.pipe(plugins.sass({outputStyle: 'compressed'}))
.pipe(gulp.dest(paths.dist + '/stylesheets'));
});
// Copy images
gulp.task('images', function() {
return gulp.src(paths.src + 'images/**/*')
.pipe(gulp.dest(paths.dist + '/images'));
});
// Watch for changes and re-run tasks
gulp.task('watchForChanges', function() {
gulp.watch(paths.src + 'javascripts/**/*', ['javascripts']);
gulp.watch(paths.src + 'stylesheets/**/*', ['sass']);
gulp.watch(paths.src + 'images/**/*', ['images']);
});
// Default: compile everything
gulp.task('default',
['copy:govuk_template:template', 'copy:govuk_template:assets', 'javascripts', 'sass', 'images']
);
// Optional: recompile on changes
gulp.task('watch',
['default', 'watchForChanges']
);

View File

@@ -2,6 +2,9 @@
"name": "notifications-admin",
"version": "0.0.1",
"description": "Admin front end for GOV.UK Notify",
"engines": {
"node": "5.0.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"postinstall": "./node_modules/bower/bin/bower install && npm run build",
@@ -16,11 +19,18 @@
"license": "MIT",
"homepage": "https://github.com/alphagov/notifications-admin#readme",
"dependencies": {
"babel-core": "6.3.26",
"babel-preset-es2015": "6.3.13",
"bower": "1.7.1",
"gulp": "3.9.0",
"gulp-add-src": "0.2.0",
"gulp-babel": "6.1.1",
"gulp-concat": "2.6.0",
"gulp-include": "2.1.0",
"gulp-jquery": "1.1.1",
"gulp-load-plugins": "1.1.0",
"gulp-sass": "2.1.1",
"gulp-uglify": "1.5.1"
"gulp-uglify": "1.5.1",
"jquery": "1.11.2"
}
}

View File

@@ -0,0 +1,17 @@
def test_should_return_list_of_all_templates(notifications_admin):
response = notifications_admin.test_client().get('/templates')
assert response.status_code == 200
def test_should_show_page_for_one_templates(notifications_admin):
response = notifications_admin.test_client().get('/templates/template')
assert response.status_code == 200
def test_should_redirect_when_saving_a_template(notifications_admin):
response = notifications_admin.test_client().post('/templates/template')
assert response.status_code == 302
assert response.location == 'http://localhost/templates'