...
 
Commits (104)
before_script:
- '[[ -d /srv/piphone/ ]] || mkdir -p /srv/piphone/'
- 'which git || (apt-get update -yq && apt-get install git -yqq)'
- 'which curl ||(apt-get update -yq && apt-get install curl -yqq)'
job composer install:
environment:
preprod
script:
- composer install
- composer update
artifacts:
paths:
- vendor/
expire_in: 1 day
stage: build
tags: [preprod]
only:
- master
job deploy preprod:
variables:
BASE_PATH: /srv/piphone/frontend/
environment:
preprod
script:
- rsync -ruvC ./ ${BASE_PATH}
stage:
deploy
tags: [preprod]
only:
- master
job install:
job deploy production:
variables:
BASE_PATH: /srv/piphone/frontend
environment:
production
script:
- chmod a+x ci/install.sh
- ./ci/install.sh
stage: deploy
tags:
- piphone
- ssh
- rsync -ruvC ./ ${BASE_PATH}
stage:
deploy
only:
- master
when: manual
tags: [piphone]
.PHONY: help doctor install reset-db translations
.DEFAULT_GOAL := help
help:
@echo "\033[33mUsage:\033[0m"
@echo " make [command]"
@echo ""
@echo "\033[33mAvailable commands:\033[0m"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | sort \
| awk 'BEGIN {FS = ":.*?## "}; {printf " \033[32m%s\033[0m___%s\n", $$1, $$2}' | column -ts___
doctor: ## Check that everything is installed to use this application
@echo "\033[1m\033[36m==> Check required dependencies\033[0m\033[21m"
@which composer >/dev/null 2>&1 && echo "\033[32mcomposer installed\033[0m" || echo "\033[31mcomposer not installed\033[0m"
@echo "\033[1m\033[36m==> Check configuration\033[0m\033[21m"
@test -s ./app/config.php && echo "\033[32mConfiguration OK\033[0m" || echo "\033[31mYou need to copy app/config.php.sample to app/config.php in order to configure your application.\033[0m"
@echo "\033[1m\033[36m==> Check optional dependencies\033[0m\033[21m"
@which msgmerge >/dev/null 2>&1 && echo "\033[32mmsgmerge installed\033[0m" || echo "\033[31mmsgmerge not installed\033[0m"
@which msgfmt >/dev/null 2>&1 && echo "\033[32mmsgfmt installed\033[0m" || echo "\033[31mmsgfmt not installed\033[0m"
@which xgettext >/dev/null 2>&1 && echo "\033[32mxgettext installed\033[0m" || echo "\033[31mxgettext not installed\033[0m"
install: ## Install the application
@echo "\033[1m\033[36m==> Install Composer dependencies\033[0m\033[21m"
@composer install
server-start: server-stop ## Launch a local server
@php -S 127.0.0.1:8000 >> .server.log &
@echo "\033[32mServer running. (http://127.0.0.1:8000)\033[0m"
server-stop: ## Stop local server if running
@ps -aux | grep "[p]hp -S 127.0.0.1:8000" | grep -v grep | awk '{print $$2}' | xargs -r -n 1 kill
@echo "\033[32mServer stopped. (http://127.0.0.1:8000)\033[0m"
translations: locales/fr_FR/LC_MESSAGES/messages.mo locales/en_US/LC_MESSAGES/messages.mo ## Generate translations
messages.pot: app/*.php templates/*.html
[ -r $@ ] || touch $@
xgettext --package-name=LQDNCampaign --package-version=2016.1 --force-po -o $@ --keyword=__ --keyword=_ --from-code=UTF-8 $^
locales/%/LC_MESSAGES/messages.po: messages.pot
msgmerge -v -U $@ $^
locales/fr_FR/LC_MESSAGES/messages.mo: locales/fr_FR/LC_MESSAGES/messages.po
msgfmt $^ -o $@
locales/en_US/LC_MESSAGES/messages.mo: locales/en_US/LC_MESSAGES/messages.po
msgfmt $^ -o $@
......@@ -11,16 +11,25 @@ class Api {
$client = new Client(['base_uri' => API_BASE, 'defaults' => [ 'headers' => [ 'Content-type' => 'applications/json']]]);
$url = API_BASE . $url . "/?format=json";
$result = $client->request('GET', $url);
return $result->getBody();
$result = $client->request('GET', $url);
return $result->getBody();
}
// Generic post function for REST API
static function post($url, $data) {
$client = new Client(['base_uri' => API_BASE, 'defaults' => [ 'headers' => [ 'Content-type' => 'applications/json']]]);
$url = API_BASE . $url . "/?format=json";
$result = $client->request('POST', $url, array(), $data);
return $result->getBody();
try {
$client = new Client(['base_uri' => API_BASE, 'defaults' => [ 'headers' => [ 'Content-type' => 'applications/json']]]);
$url = API_BASE . $url . "/?format=json";
$result = $client->request('POST', $url, ['body' => json_encode($data), "headers" => ['Content-Type' => 'application/json']]);
return $result->getBody();
} catch (Exception $e) {
echo $e;
if ( $e->hasResponse()) {
echo $e->getResponse()->getBody();
} else {
echo $e;
}
}
}
// Asking for campaign informations
......@@ -37,8 +46,8 @@ class Api {
// Asking for a single contact
static function get_contact($id) {
$json = Api::get("campaigns/" . CAMPAIGN_ID . "/contacts/" . $id);
return json_decode($json, true);
$json = Api::get("campaigns/" . CAMPAIGN_ID . "/contacts/" . $id);
return json_decode($json, true);
}
// Asking for complete list of groups
......@@ -47,9 +56,15 @@ class Api {
return json_decode($json, true);
}
// //Asking for complete list of group types
static function get_group_types() {
$json = Api::get("campaigns/" . CAMPAIGN_ID . "/grouptypes");
return json_decode($json, true);
}
// Asking for complete list of feedback categories
static function get_feedback_categories() {
$json = Api::get("campaigns/" . CAMPAIGN_ID . "/feedback/categories");
$json = Api::get("campaigns/" . CAMPAIGN_ID . "/categories");
return json_decode($json, true);
}
......@@ -59,16 +74,21 @@ class Api {
return json_decode($json, true);
}
// Asking for organization details
static function get_organization() {
$json = Api::get("campaigns/" . CAMPAIGN_ID . "/organization");
return json_decode($json, true);
}
// Posting a feedback
static function post_feedback($contact_id, $comment, $feedback_category='') {
$data = array(
"contact_id"=>$contact_id,
"callee"=>$contact_id,
"comment"=>$comment,
"feedback_category"=>$feedback_category
"category"=>$feedback_category
);
$json = Api::post("campaigns/" . CAMPAIGN_ID . "/feedback/add", $data);
$json = Api::post("campaigns/" . CAMPAIGN_ID . "/feedbacks", $data);
return json_decode($json, true);
}
};
......@@ -20,12 +20,20 @@ class Controller {
// Init campaign informations
$f3->set('campaign', Api::get_campaign());
// Get organization informations
$f3->set('organization', Api::get_organization());
// Init contacts list if needed
if (!$f3->exists('contacts')) {
$f3->set('contacts', Api::get_contacts(), API_TIMEOUT);
}
// Workaround for caching variables
$f3->set('contacts', $f3->get('contacts'));
$groups = Api::get_groups();
$f3->set('groups', $groups);
$group_types = Api::get_group_types();
$f3->set('group_types', $group_types);
}
// Function called after routing
......
......@@ -14,11 +14,12 @@ class Main extends Controller {
function home($f3,$args) {
// Select a random contact
//TODO: use weight for random
$rand_id = rand(0, count($f3->get('contacts'))-1);
$contacts = $f3->get('contacts');
$f3->set('contact', $contacts[$rand_id]);
$f3->set('random', rand(0, 2));
$f3->set('block_content', 'home.html');
$groups = Api::get_groups();
$f3->set('groups', $groups);
$f3->set('block_content', 'home.html');
}
/*
......@@ -31,11 +32,14 @@ class Main extends Controller {
//GET
if ($f3->get('VERB') == 'GET'){
$categories = Api::get_feedback_categories();
$f3->set('feedback_categories', $categories['categories']);
$f3->set('feedback_categories', $categories);
$contact_id = $f3->get('POST.contact_id');
$f3->set("contact_id", $contact_id);
$arguments = Api::get_arguments();
$f3->set('arguments', $arguments);
$f3->set('block_content', 'feedbackform.html');
}
//POST
......@@ -50,13 +54,6 @@ class Main extends Controller {
}
}
/*
* Feedback SIP function
*/
function feedbacksip($f3, $args) {
}
/*
* call Page
* Form to call
......@@ -66,52 +63,41 @@ class Main extends Controller {
function call($f3, $args) {
//GET
if ($f3->get('VERB') == 'GET') {
$f3->set('contact', Api::get_contact($args['id']));
$f3->set('block_content', 'call.html');
$categories = Api::get_feedback_categories();
$f3->set('feedback_categories', $categories);
$f3->set('contact_id', $args['id']);
$f3->set('block_content', 'feedbackform.html');
$f3->set("contact_id", $contact_id);
$arguments = Api::get_arguments();
$f3->set('arguments', $arguments);
$f3->set('block_content', 'feedbackform.html');
}
//POST
elseif ($f3->get('VERB') == 'POST'){
// Create the call
// Generate a jwt token
$token = JWT::encode(array('api' => JWT_TOKEN), JWT_KEY);
// To get the callee, we have the callee_id in the form. Using that
// to load the callee and retrieve its number.
$contact = Api::get_contact($args['id']);
$f3->set('contact', $contact);
$data = array('api' => 'piphone', 'caller' => $f3->get('POST.phone'), 'callee' => $contact['phone'], 'token' => $token);
// We want to generate a UNIQUE-ID (doesn't need to be cryptogaphically unique though
$call_id = uniqid();
$f3->set('CALL_ID', $call_id);
// Create the call
$client = new GuzzleHttp\Client(['base_uri' => SIP_API]);
try {
$res = $client->post("calls/$call_id", ['query' => $data]);
$f3->set('call', json_decode($res->getBody()));
$f3->set('VERB', 'GET');
$this->feedbackform($f3, $args);
} catch (RequestException $e) {
echo Psr7\str($e->getRequest());
if ($e->hasResponse()) {
echo Psr7\str($e->getResponse());
}
}
}
// To get the callee, we have the callee_id in the form. Using that
// to load the callee and retrieve its number.
$contact = Api::get_contact($args['contact_id']);
$f3->set('contact', $contact);
$call_id = str_replace('.', '-', uniqid('', true));
}
}
function contactslist($f3, $args) {
$contacts = Api::get_contacts();
$f3->set('contacts', $contacts);
$f3->set('random', 2);
$f3->set('block_content', 'contactslist.html');
}
function argumentation($f3, $args) {
function campaign($f3, $args) {
$arguments = Api::get_arguments();
$f3->set('arguments', $arguments);
$f3->set('block_content', 'argumentation.html');
$f3->set('block_content', 'campaign.html');
}
function piphone($f3, $args) {
$f3->set('block_content', 'piphone.html');
}
};
;
<?php
use \Firebase\JWT\JWT;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
use GuzzleHttp\Exception\RequestException;
class SIP extends Controller {
function beforeRoute($f3, $args) {
// Include configuration
require_once('config.php');
}
function afterRoute($f3, $args) {
// We return only JSON, so no templates
echo $f3->get('response');
}
// Getting a call from the SIP_API
function feedbacksip($f3, $args) {
$token = JWT::encode(array('api' => JWT_TOKEN), JWT_KEY);
$call_id = $args['callid'];
$data = array('api' => JWT_TOKEN,
'token' => $token
);
$client = new GuzzleHttp\Client(['base_uri' => SIP_API]);
try {
$token = JWT::encode(array('api' => JWT_TOKEN, 'nbf' => time() - 5, 'exp' => time() + 10*60, 'iat' => time()), JWT_KEY);
$res = $client->get("calls/$call_id", ['query' => $data]);
$f3->set('response', (GuzzleHttp\Psr7\copy_to_string($res->getBody())));
} catch (RequestException $e) {
if ($e->hasResponse()) {
$f3->set('response', Psr7\str($e->getResponse()));
}
$f3->set('response', Psr7\str($e->getRequest()));
}
}
}
......@@ -6,7 +6,7 @@ AUTOLOAD=app/
UI=templates/
; Cache
CACHE = true
;CACHE = false
; Debug level
DEBUG=3
......@@ -15,4 +15,3 @@ DEBUG=3
LANGUAGE='fr'
; Global variables of website
<?php
/* Configuration file for Campaign manager */
define("CAMPAIGN_ID","1");
define("CAMPAIGN_ID", getenv("CAMPAIGN_ID") ? getenv("CAMPAIGN_ID") : "1");
define("API_BASE","http://localhost/restapi/");
define("API_TIMEOUT","1");
define("JWT_KEY", "YOUR_KEY");
......
......@@ -4,8 +4,9 @@ GET @home:/=Main->home
GET|POST @feedback:/feedback=Main->feedbackform
GET|POST @call:/call/@id=Main->call
GET @contactslist:/contactslist=Main->contactslist
POST @feedbacksip:/feedbacksip=Main->feedbacksip
GET @argumentation:/argumentation=Main->argumentation
GET @feedbacksip:/calls/@callid=SIP->feedbacksip
GET @campaign:/campaign=Main->campaign
GET @piphone:/piphone=Main->piphone
; Fake REST API
GET /restapi/campaigns/@id=RestApi->campaign
......
......@@ -10,9 +10,9 @@ fi
export CI_PROJECT_NAME=$(basename $CI_PROJECT_DIR)
if [ -z "$CI_BUILD_REPO" ]
if [ -z "$CI_REPOSITORY_URL" ]
then
echo "CI_BUILD_REPO undefined"
echo "CI_REPOSITORY_URL undefined"
exit 1
fi
......@@ -22,7 +22,7 @@ then
cd $CI_PROJECT_NAME
git pull origin master
else
git clone $CI_BUILD_REPO $CI_PROJECT_NAME
git clone $CI_REPOSITORY_URL $CI_PROJECT_NAME
cd $CI_PROJECT_NAME
fi
......
#: templates/campaign.html:5 templates/intro.html:11
msgid "Act Now!"
msgstr ""
#: templates/campaign.html:12 templates/intro.html:23
msgid "Visit Us!"
msgstr ""
#: templates/feedbackform.html:4
msgid "0. Initialising the call"
msgstr ""
#: templates/feedbackform.html:5
msgid "1. Calling you"
msgstr ""
#: templates/feedbackform.html:6
msgid "2. When ready, please dial 1 on your phone"
msgstr ""
#: templates/feedbackform.html:7
msgid "3. Calling "
msgstr ""
#: templates/feedbackform.html:8
msgid "4. Connected to "
msgstr ""
#: templates/feedbackform.html:27
msgid "Phone number"
msgstr ""
#: templates/feedbackform.html:60
msgid "Feedback Form"
msgstr ""
#: templates/feedbackform.html:61
msgid ""
"Please tell us what happened if you were able to talk to someone. Your "
"feedback is important to us."
msgstr ""
#: templates/footer.html:6
msgid "This campaign is powered by the piphone"
msgstr ""
#: templates/footer.html:14
msgid "Get in touch with LQDN"
msgstr ""
#: templates/footer.html:15
msgid "Code licensed under the AGPL v3.0+"
msgstr ""
#: templates/footer.html:7
msgid ""
"The piphone is a campaigning tool developped by <a href=\"https://www."
"laquadrature.net/\" class=\"blue-grey-text text-darken-2\">La Quadrature du "
"Net</a>"
msgstr ""
#: templates/contact.html:30 templates/home.html:36
msgid "Call for free"
msgstr ""
#: templates/contact.html:36
msgid "Your phone number:"
msgstr ""
#: templates/contact.html:46
msgid "More info"
msgstr ""
#: templates/contact.html:33
msgid ""
"If you want to call for free, you must provide us with your phone number "
msgstr ""
#: templates/toolbar.html:4
msgid "Act"
msgstr ""
#: templates/toolbar.html:5
msgid "About the campaign"
msgstr ""
#: templates/toolbar.html:6
msgid "About the piphone"
msgstr ""
#: templates/sidebar.html:18
msgid "Home"
msgstr ""
#: templates/sidebar.html:19
msgid "Choose a representative to call"
msgstr ""
#: templates/sidebar.html:20 templates/argumentation.html:3
msgid "Arguments"
msgstr ""
#: templates/argumentation.html:5
msgid ""
"Those are arguments we provide you to help you convince the representative "
"you will call."
msgstr ""
#: templates/call.html:6
msgid "Reach out to "
msgstr ""
#: templates/call.html:11
msgid "Call at your expense"
msgstr ""
#: templates/call.html:12
msgid ""
"Here is the number of the selected MEP - you can either dial it from your "
"phone or push the button if any VoIP client is installed on your device."
msgstr ""
#: templates/thankyou.html:4
msgid "Thank you for your call! &lt;3"
msgstr ""
#: templates/thankyou.html:6
msgid "Call another representative"
msgstr ""
#: templates/piphone.html:4
msgid "What is the piPhone?"
msgstr ""
#: templates/piphone.html:7
msgid ""
"The piphone is a tool developped by <a href=\"https://www.laquadrature.net"
"\">La Quadrature du Net</a>, a French NGO specialised in digital rights "
"issues."
msgstr ""
#: templates/piphone.html:8
msgid ""
"Its purpose is to ease mobilisation of people toward influencing the "
"representatives elements of democracy, by removing the cost of such calls "
"and gathering all needed data."
msgstr ""
#: templates/piphone.html:11
msgid ""
"The source code is distributed under the term of the AGPL v3.0+ and is open "
"to contribution on <a href=\"https://git.laquadrature.net\">LQDN's own "
"gitlab</a>"
msgstr ""
#: templates/home.html:5
msgid "Call a representative and make your voice heard!"
msgstr ""
#: templates/home.html:40
msgid "We need your phone number to initiate the call"
msgstr ""
#: templates/campaign.html:5 templates/intro.html:11
msgid "Act Now!"
msgstr ""
#: templates/campaign.html:12 templates/intro.html:23
msgid "Visit Us!"
msgstr ""
#: templates/feedbackform.html:4
msgid "0. Initialising the call"
msgstr ""
#: templates/feedbackform.html:5
msgid "1. Calling you"
msgstr ""
#: templates/feedbackform.html:6
msgid "2. When ready, please dial 1 on your phone"
msgstr ""
#: templates/feedbackform.html:7
msgid "3. Calling "
msgstr ""
#: templates/feedbackform.html:8
msgid "4. Connected to "
msgstr ""
#: templates/feedbackform.html:27
msgid "Phone number"
msgstr ""
#: templates/feedbackform.html:60
msgid "Feedback Form"
msgstr ""
#: templates/feedbackform.html:61
msgid ""
"Please tell us what happened if you were able to talk to someone. Your "
"feedback is important to us."
msgstr ""
#: templates/footer.html:6
msgid "This campaign is powered by the piphone"
msgstr ""
#: templates/footer.html:14
msgid "Get in touch with LQDN"
msgstr ""
#: templates/footer.html:15
msgid "Code licensed under the AGPL v3.0+"
msgstr ""
#: templates/footer.html:7
msgid ""
"The piphone is a campaigning tool developped by <a href=\"https://www."
"laquadrature.net/\" class=\"blue-grey-text text-darken-2\">La Quadrature du "
"Net</a>"
msgstr ""
#: templates/contact.html:30 templates/home.html:36
msgid "Call for free"
msgstr ""
#: templates/contact.html:36
msgid "Your phone number:"
msgstr ""
#: templates/contact.html:46
msgid "More info"
msgstr ""
#: templates/contact.html:33
msgid ""
"If you want to call for free, you must provide us with your phone number "
msgstr ""
#: templates/toolbar.html:4
msgid "Act"
msgstr ""
#: templates/toolbar.html:5
msgid "About the campaign"
msgstr ""
#: templates/toolbar.html:6
msgid "About the piphone"
msgstr ""
#: templates/sidebar.html:18
msgid "Home"
msgstr ""
#: templates/sidebar.html:19
msgid "Choose a representative to call"
msgstr ""
#: templates/sidebar.html:20 templates/argumentation.html:3
msgid "Arguments"
msgstr ""
#: templates/argumentation.html:5
msgid ""
"Those are arguments we provide you to help you convince the representative "
"you will call."
msgstr ""
#: templates/call.html:6
msgid "Reach out to "
msgstr ""
#: templates/call.html:11
msgid "Call at your expense"
msgstr ""
#: templates/call.html:12
msgid ""
"Here is the number of the selected MEP - you can either dial it from your "
"phone or push the button if any VoIP client is installed on your device."
msgstr ""
#: templates/thankyou.html:4
msgid "Thank you for your call! &lt;3"
msgstr ""
#: templates/thankyou.html:6
msgid "Call another representative"
msgstr ""
#: templates/piphone.html:4
msgid "What is the piPhone?"
msgstr ""
#: templates/piphone.html:7
msgid ""
"The piphone is a tool developped by <a href=\"https://www.laquadrature.net"
"\">La Quadrature du Net</a>, a French NGO specialised in digital rights "
"issues."
msgstr ""
#: templates/piphone.html:8
msgid ""
"Its purpose is to ease mobilisation of people toward influencing the "
"representatives elements of democracy, by removing the cost of such calls "
"and gathering all needed data."
msgstr ""
#: templates/piphone.html:11
msgid ""
"The source code is distributed under the term of the AGPL v3.0+ and is open "
"to contribution on <a href=\"https://git.laquadrature.net\">LQDN's own "
"gitlab</a>"
msgstr ""
#: templates/home.html:5
msgid "Call a representative and make your voice heard!"
msgstr ""
#: templates/home.html:40
msgid "We need your phone number to initiate the call"
msgstr ""
The MIT License (MIT)
Copyright (c) 2014-2017 Materialize
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
![alt tag](https://raw.github.com/dogfalo/materialize/master/images/materialize.gif)
===========
[![Travis CI](https://travis-ci.org/Dogfalo/materialize.svg?branch=master)](https://travis-ci.org/Dogfalo/materialize)[![devDependency Status](https://david-dm.org/Dogfalo/materialize/dev-status.svg)](https://david-dm.org/Dogfalo/materialize#info=devDependencies)[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/Dogfalo/materialize?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[Materialize](http://materializecss.com/), a CSS Framework based on material design
### Current Version : v0.97.8
## Sass Requirements:
- Ruby Sass 3.3+, LibSass 0.6+
## Supported Browsers:
Chrome 35+, Firefox 31+, Safari 7+, IE 10+
## Changelog
- v0.97.8 (October 30th, 2016)
- **Refactored Modal plugin**
- Tabs now supported in navbar
- Chips data can now be reinitiailized
- Minor side nav fixes
- FAB to toolbar component added
- Fixed dropdown options bug
- v0.97.7 (July 23rd, 2016)
- Basic horizontal cards
- Carousel bug fixes and new features
- Updated sidenav styles and new component
- Meteor package now supports Sass
- Autocomplete form component
- Chips jQuery plugin
- v0.97.6 (April 1st, 2016)
- **Removed deprecated material icons from project**
- **Changed /font directory to /fonts**
- Datepicker and ScrollSpy now compatible with jQuery 2.2.x
- Responsive tables now work with empty cells
- Added focus states to checkboxes, switches, and radio buttons
- Sidenav and Modals no longer cause flicker with scrollbar
- Materialbox overflow and z-index issues fixed
- Added new option for Card actions within a Card reveal
- v0.97.5 (December 21st, 2015)
- Fixed Meteor package crash
## Contributing
[Please read CONTRIBUTING.md for more information](CONTRIBUTING.md)
## Testing
We use Jasmine as our testing framework and we're trying to write a robust test suite for our components. If you want to help, [here's a starting guide on how to write tests in Jasmine](https://docs.google.com/document/d/1dVM6qGt_b_y9RRhr9X7oZfFydaJIEqB9CT7yekv-4XE/edit?usp=sharing)
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
.collection.contact .wrapper {
overflow: auto;
overflow-x: hidden;
cursor: pointer;
height: 70vh;
}
.piphone-btn {
position: relative;
left: 0;
transform: translateX(-50%);
float: left;
}
.repeat-btn {
position: relative;
top: 0;
left: -56px;
top: 65px;
transform: translateX(-50%);
float: right;
}
.chip.committee {
font-size: 11px;
line-height: 22px;
height: 22px;
padding: 0 10px;
}
body {
display: flex;
min-height: 100vh;
flex-direction: column;
}
main {
flex: 1 0 auto;
}
.small-icons {
height: 48px;
width: 48px;
}
.json-data {
display: none;
}
@media (max-width: 768px) {
.pasmodal p, .pasmodal div, .pasmodal span, .pasmodal hr {
margin: 0.1em!important;
padding: 0!important;
text-align: center;
}
.modal ul {
padding: 0.1em!important;
}
}
.panel {
margin: 0.5em !important;
padding: 0.5em !important;
}
.form-inline > * {
margin: 0.5em!important;
}
.well {
background: #777777;
color: #FFFFFF;
}
h1 {
font-size: 2em;
}
a {
color: #000000;
}
.btn-info {
background: #777777;
border-color: #777777;
}
.btn-info:hover {
background: #666666;
border-color: #666666;
}
/* GLOBAL */
section {
max-width: 60em;
margin: auto;
padding: .5em 1em;
}
/* HEADER */
#header {
color: #487ED6;
height: 80px;
}
#header img {
height: 64px;
float: left;
}
#header h1 {
padding-left: 120px;
font-weight: bold;
font-size: 2em;
margin: 0;
}
#header h2 {
padding-left: 120px;
font-weight: bold;
font-size: 1em;
margin: 0;
}
/* LANGUAGES */
#languages {
float: right;
text-align: right;
margin: .5em;
}
/* TOOLBAR */
#toolbar {
}
#toolbar ul {
margin: 0;
padding: 0;
}
#toolbar li {
display: inline-block;
background-color: #ccc;
padding: .5em 1em;
}
#callModal {
visibility:hidden;
}
#callModal:target {
visibility:visible;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
}
.cmodal .panel{
margin: auto;
background: rgba(255, 255, 255, 1);
max-width: 80%;
min-width: 50%;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
<section>
<repeat group="{{ @arguments }}" value="{{ @argument }}">
<h3>{{ @argument.title }}</h3>
<p>{{ @argument.text }}</p>
</repeat>
</section>
<div class="row">
<div class="container">
<h3 class="grey-blue-text text-darken-4 header center">{{ _("Arguments") }}</h3>
<p class="light">
{{ _("Those are arguments we provide you to help you convince the representative you will call.") }}
</p>
<ul class="collapsible popout" data-collapsible="accordions">
<repeat group="{{ @arguments }}" value="{{ @argument }}" counter="{{ @key }}">
<li>
<div class="collapsible-header">{{ @argument.title}}</div>
<div class="collapsible-body">{{ @argument.text | raw }}</div>
</li>
</repeat>
</ul>
</div>
</div>
<include href="header.html" />
<include href="{{ @block_content }}" />
<include href="call.html" />
<include href="footer.html" />
<div class="overlay" id="callModal">
<div class="cmodal container-fluid panel panel-default">
<div class="panel-heading">
<h2>{{ _("Reach out to ");}} {{ @contact.first_name }}&nbsp;{{ @contact.last_name }}
<a type="button" class="btn close" href="#" aria-label="Close"><span aria-hidden="true">&times;</span></a></h2>
</div>
<div class="row panel-body">
<div class="col-sm-6">
<h2>{{ _("Call for free") }}</h2>
<form method="post" action="/call/{{ @contact.id }}">
<input type="text" id="contact_id" name="contact_id" hidden="hidden" value="{{ @contact.id }}">
<p>{{ _("If you want to call for free, you must provide us with your phone number (the PiPhone will call that number to initiate the communication).") }}</p>
<p>
<label for="phone">{{_("Your phone number:") }}</label>
<input type="text" name="phone" id="phone" placeholder="+33123456789001" />
</p>
<p>
{{ _('Starting with your <a href="https://en.wikipedia.org/wiki/List_of_country_calling_codes#Zones_3-4:_Europe">country code</a>, without the initial 0') }}
</p>
<input class="btn btn-default" type="submit" value="{{ _("I'm ready, call me") }}" />
</form>
</div>
<div class="col-sm-6">
<h2>{{ _("Call at your expense") }}</h2>
<p>{{ _("If you don't want to call for free, here is the number of the current MEP (you can either dial it from your phone or push the button if any VoIP client is installed on your device.") }}</p>
<a class="btn btn-default" href="tel:{{ @contact.phone }}" target="_blank">☎ {{ @contact.phone }}</a>
</div>
</div>
</div>
</div>
<div class="section">
<div class="container">
<h3 class="blue-grey-text text-darken-4 bold center">{{ @organization.name }}</h1>
<p class="">{{ @organization.description }}</p>
<a href="/#navbar" class="btn waves-effect waves-light leat accent-2 center">{{ _("Act Now!") }}</a>
</div>
</div>
<div class="section">
<div class="container">
<h3 class="blue-grey-text text-darken-4 center bold">{{ @campaign.title }}</h1>
<p class="">{{ @campaign.description }}</p>
<a href="{{ @organization.website }}" class="btn waves-effect waves-light blue accent-2 center">{{ _("Visit Us!") }}</a>
</div>
</div>
<hr>
<div class="section">
<div class="container">
<include href="argumentation.html">
</div>
</div>
<!-- contact page -->
<div class="container-fluid panel panel-default pasmodal">
<div class="row">
<div class="col-sm-12">
<!-- image à gauche -->
<div class="col-md-2 col-sm-4 hidden-xs">
<img class= "img-responsive img-circle" src="static/img/base{{ @random }}.jpg" width="140px" alt="mep">
<!-- infos -->
<div class="visible-sm-block">
<address style="margin-top:1em;">
<strong>{{ _("Informations") }}</strong><br>
</address>
</div>
<!-- /infos -->
</div>
<!-- /image à gauche -->
<!-- pas image -->
<div class="col-md-10 col-sm-8">
<div class="row">
<!-- gauche -->
<div class="col-md-8">
<!-- titre -->
<div>
<h1 class="text-center" id="name">{{ @contact.first_name }} <br class="visible-xs-block">{{ @contact.last_name }}</h1>
<!-- actions -->
<div class="text-center row">
<p class="lead text-center">
<abbr title="{{ _("Phone number") }}">{{ _("Phone number") }}:</abbr> <a href="tel: {{ @contact.phone }}">{{ @contact.phone }}</a>
<br class="visible-xs-block">
<a type="button" class="btn btn-default" title="{{ _("Call now") }}" href="#callModal"><span class="glyphicon glyphicon-earphone"></span></button>
<a type="button" class="btn btn-info" title="{{ _("Get more info...") }}" href="#"><span class="glyphicon glyphicon-info-sign"></span></a>
<a type="button" class="btn btn-default visible-xs-inline-block" title="{{ _("Choose someone else") }}" href="#"><span class="glyphicon glyphicon-refresh"></span></a>
</p>
</div>
<!-- /actions -->
</div>
<!-- /titre -->
<!-- groupes -->
<div>
<dl class="dl-horizontal hidden-xs" id="resume">
<repeat group="{{ @contact.groups }}" value="{{ @group }}">
<check if="{{ @group.type!='Committee' }}">
<dt>{{ @group.type }}:</dt>
<dd><a href="#">{{ @group.name }}</a></dd>
</check>
</repeat>
</dl>
</div>
<!-- /groupes -->
<!-- committees -->
<div>
<ul class="list-inline" id="committee">
<repeat group="{{ @contact.groups }}" value="{{ @group }}">
<check if="{{ @group.type=='Committee' }}">
<li><abbr title="{{ @group.name }}" class="initialism"><ahref="https://memopol.lqdn.fr/europe/parliament/committee/{{ @group.name }}/"><div class="well well-sm">{{ @group.name }}</div></a></abbr></li>
</check>
</repeat>
</ul>
</div>
<!-- /committees -->
</div>
<!-- /gauche -->
<!-- droite -->
<div class="col-md-4">
<!-- infos -->
<div class="hidden-xs hidden-sm text-right">
<address style="margin-top:1em;">
<strong>{{ _("Informations") }}</strong><br>
</address>
</div>
<!-- /infos -->
<hr class="hidden-xs">
<!-- random -->
<div class="text-center">
<form class="form-inline" role="form">
<!-- <div class="form-group hidden-xs">
<label class="" for="country">Pays : </label>
<select class="form-control" name="country" id="country" onchange="$('#selcountry').submit()"><option value="">-- Toute l'Europe -- </select>
</div> --><!-- /form-group -->
<button type="submit" class="btn btn-default hidden-xs" title="Choisir un autre député"><span class="glyphicon glyphicon-refresh"></span></button>
</form>
</div>
<!-- /random -->
</div>
<!-- /droite -->
</div>
<!-- /row-->
</div>
<!--/.col-sm-6--><!-- /pas image -->
</div>
<div class="card sticky-action">
<div class="card-image">
<img src="/static/img/124786.jpg">
<a class="btn-floating btn-large halfway-fab waves-effect activator green"><i class="material-icons">phone</i></a>
</div>
<div class="card-content">
<span class="card-title grey-text text-darken-4">
{{ @contact.first_name }} {{ @contact.last_name }}
</span>
<repeat group="{{ @group_types }}" value="{{ @group_type }}">
<ul>{{ @group_type.name }}
<repeat group="{{ @contact.groups }}" value="{{ @group }}">
<check if="{{ @group.type == @group_type.name }}">
<li class="chip">
<check if="{{ @group.media }}">
<img src="{{ @group.media }}" alt="{{ @group.type }}">
</check>
{{ @group.name }}
</li>
</check>
</repeat>
</ul>
</repeat>
</div>
<div class="card-reveal">
<span class="card-title grey-text text-darken-4">{{ @contact.first_name }} {{ @contact.last_name }}
<i class="material-icons right">close</i>
</span>
<h5>{{ _("Call this representative" }}</h5>
<form method="post" action="/call/{{ @contact.id }}">
<input type="text" id="contact_id" name="contact_id" hidden="hidden" value="{{ @contact.id }}">
<a type="button" href="tel:{{ @contact.phone }}" target="_blank">{{ @contact.phone }}</a>
<input class="btn btn-default" type="submit" value="{{ _('Make the call') }}" />
</form>
</div>
</div>
<!-- contact page -->
<div class="col s12 m4">
<ul class="collection contact">
<nav class="nav-extended teal">
<div class="nav-wrapper">
<div class="input-field">
<input type="search" id="search" class="autocomplete">
<label class="label-icon" for="search"><i class="material-icons">search</i></label>
</div>
<repeat group="{{ @group_types }}" value="{{ @group_type }}">
<div class="input-field">
<select class="filter" id="{{ @group_type.name }}" name="{{ @group_type.name }}">
<option value="-1">-- {{ @group_type.name }}</option>
<repeat group="{{ @groups }}" value="{{ @group }}">
<check if="{{ @group.type == @group_type.name }}">
<option value="{{ @group.id }}"> {{ @group.name }}</option>
</check>
</repeat>
</select>
</div>
</repeat>
</div>
</nav>
<div class="json-data">
{{ json_encode(@contacts) }}
</div>
<div class="wrapper">
</div>
</ul>
</div>
<!-- contacts list -->
<section id="contact_list">
<!--
TODO: Pagination des contacts
-->
<repeat group="{{ @contacts }}" value="{{ @contact }}">
<include href="contact.html" />
</repeat>
</section>
<!-- contacts list -->
<script>
$(document).ready(function() {
$('input.autocomplete').autocomplete({
data: {
<repeat group="{{ @contacts }}" value="{{ @contact }}">
"{{ @contact.full_name }}": <check if="{{ @contact.photo == '' }}">null<false>"{{ @contact.photo }}"</false></check>,
</repeat>
},
limit: 20,
});
$('select.filter').change(function() {
// We must get the contact list as it is before apllying this specific filter
// Meaning, the full contact list plus all the other filter.
// We just need to call it on all the select elements.
contacts = JSON.parse(contacts_json);
$('select.filter').each(function() {
var filter = $(this);
var filter_group = $(this).val();
contacts = contacts.filter(filter_mep, filter);
});
list_meps(contacts, wrapper);
});
$('#search').change(function() {
contacts = contacts.filter(function(contact) {
return contact['full_name'].startsWith($('#search').val());
});
list_meps(contacts, $('.contact .wrapper'));
});
var wrapper = $('.contact .wrapper');
var contacts_json = $('.contact .json-data').text()
var contacts = JSON.parse(contacts_json);
list_meps(contacts, wrapper);
random_mep();
$('select').material_select();
});
function filter_mep(contact) {
var filter_group = $(this).val();
if (Number(filter_group) === -1) {
return true
};
var keep = false
contact['groups'].forEach(function(group) {
if (group['id'] === Number(filter_group)) {
keep = true;
};
});
return keep;
}
function random_mep() {
var random = Math.floor((Math.random() * $('.collection-item').length));
update_mep($('.collection-item').eq(random));
}
function list_meps(contacts, wrapper) {
contacts = contacts.sort(function(a, b) {
if (a['last_name'] < b['last_name']) {
return -1;
};
if (a['last_name'] > b['last_name']) {
return 1;
};
return 0;
});
// We need to empty the wrapper first
wrapper.empty();
contacts.forEach(function(contact) {
display_mep(contact).appendTo(wrapper);
});
};
function display_mep(contact) {
var li = $('<li />')
.addClass('collection-item avatar')
.click(function() {
update_mep($(this));
$('html, body').animate({
scrollTop: $('#navbar').offset().top
}, 500);
})
.attr('data-json', JSON.stringify(contact));
var country = contact['groups'].filter(function(group) {
return group['type'] === 'country';
});
var group = contact['groups'].filter(function(group) {
return group['type'] === 'group';
});
if ( group.length > 0 ) {
var group_src = 'http://www.europarl.europa.eu/ep_framework/img/group/group_iconsmall_'
+ group[0]['name'].toLowerCase().replace('/','') + '.png';
$('<img />').attr('src', group_src)
.appendTo(li);
};
if ( country.length > 0 ) {
var country_src = 'http://www.europarl.europa.eu/ep_framework/img/flag/flag_icon_'
+ country[0]['name'].toLowerCase().replace('/','') + '.gif';
$('<img />').addClass('circle')
.attr('src', country_src)
.appendTo(li)
} else {
$('<img />').addClass('circle')
.attr('src', '/static/img/placeholder.jpg')
.appendTo(li)
};
$('<span />').addClass('title')
.text(contact['full_name'])