Merge branch 'master' into setup/pagination

This commit is contained in:
bumi authored and GitHub committed 2020-04-28 09:41:20 +02:00
commit 13086b6a37
80 files changed
+564 -236

No files matched your search

+1 -1
View File
@@ -28,7 +28,7 @@ $weight-bold: 400;
@import "checkmark-icon";
@import 'demo';
@import 'highlight';
@import 'prism';
.field_with_errors input {
border-color: $red;
@@ -1,5 +0,0 @@
code.html .hljs-tag:first-child .hljs-attr, code.html .hljs-tag:first-child .hljs-string {
font-weight: bold;
font-size: 1.2em;
}
+131
View File
@@ -0,0 +1,131 @@
/* PrismJS 1.20.0
https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript&plugins=custom-class */
/**
* okaidia theme for JavaScript, CSS and HTML
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
* @author ocodia
*/
code.language-html > .prism-tag:first-child {
font-weight: bold;
font-size: 1.1em;
}
code[class*="language-"],
pre[class*="language-"] {
color: #f8f8f2;
background: none;
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #272822;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.prism-token.prism-comment,
.prism-token.prism-prolog,
.prism-token.prism-doctype,
.prism-token.prism-cdata {
color: slategray;
}
.prism-token.prism-punctuation {
color: #f8f8f2;
}
.prism-token.prism-namespace {
opacity: .7;
}
.prism-token.prism-property,
.prism-token.prism-tag,
.prism-token.prism-constant,
.prism-token.prism-symbol,
.prism-token.prism-deleted {
color: #f92672;
}
.prism-token.prism-boolean,
.prism-token.prism-number {
color: #ae81ff;
}
.prism-token.prism-selector,
.prism-token.prism-attr-name,
.prism-token.prism-string,
.prism-token.prism-char,
.prism-token.prism-builtin,
.prism-token.prism-inserted {
color: #a6e22e;
}
.prism-token.prism-operator,
.prism-token.prism-entity,
.prism-token.prism-url,
.prism-language-css .prism-token.prism-string,
.prism-style .prism-token.prism-string,
.prism-token.prism-variable {
color: #f8f8f2;
}
.prism-token.prism-atrule,
.prism-token.prism-attr-value,
.prism-token.prism-function,
.prism-token.prism-class-name {
color: #e6db74;
}
.prism-token.prism-keyword {
color: #66d9ef;
}
.prism-token.prism-regex,
.token.important {
color: #fd971f;
}
.prism-token.prism-important,
.prism-token.prism-bold {
font-weight: bold;
}
.prism-token.prism-italic {
font-style: italic;
}
.prism-token.prism-entity {
cursor: help;
}
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module ApplicationCable
class Connection < ActionCable::Connection::Base
end
+3 -1
View File
@@ -1,9 +1,11 @@
# frozen_string_literal: true
class ApplicationController < ActionController::Base
helper_method :current_user, :logged_in?
include Pagy::Backend
def require_login
redirect_to login_url unless current_user.present?
redirect_to login_url if current_user.blank?
end
def current_user
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class FileUploadsController < ApplicationController
def show
@form = Form.find_by!(token: params[:form_id])
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'google/apis/sheets_v4'
require 'google/api_client/client_secrets'
class FormsController < ApplicationController
+2 -1
View File
@@ -1,5 +1,6 @@
class HomeController < ApplicationController
# frozen_string_literal: true
class HomeController < ApplicationController
def index
redirect_to forms_url if logged_in?
end
+10 -10
View File
@@ -1,5 +1,6 @@
class OauthsController < ApplicationController
# frozen_string_literal: true
class OauthsController < ApplicationController
# Sends the user on a trip to the provider,
# and after authorizing there back to the callback url.
def oauth
@@ -9,26 +10,25 @@ class OauthsController < ApplicationController
def callback
provider = params[:provider]
if @user = login_from(provider)
redirect_to root_path, :notice => "Logged in from #{provider.titleize}!"
redirect_to root_path, notice: "Logged in from #{provider.titleize}!"
else
begin
@user = create_from(provider)
if authentication = @user.authentications.find_by(provider: provider)
authentication.update({
access_token: @access_token.token,
refresh_token: @access_token.refresh_token,
expires_at: Time.at(@access_token.expires_at)
})
access_token: @access_token.token,
refresh_token: @access_token.refresh_token,
expires_at: Time.zone.at(@access_token.expires_at)
})
end
reset_session
auto_login(@user)
redirect_to root_path, :notice => "Logged in from #{provider.titleize}!"
rescue
redirect_to root_path, notice: "Logged in from #{provider.titleize}!"
rescue StandardError
Rails.logger.error("Failed to login from #{provider}")
redirect_to root_path, :alert => "Failed to login from #{provider.titleize}!"
redirect_to root_path, alert: "Failed to login from #{provider.titleize}!"
end
end
end
end
+2 -2
View File
@@ -1,5 +1,6 @@
class SessionsController < ApplicationController
# frozen_string_literal: true
class SessionsController < ApplicationController
def new
reset_session
end
@@ -8,5 +9,4 @@ class SessionsController < ApplicationController
reset_session
redirect_to root_url
end
end
+8 -2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'google/apis/sheets_v4'
class SubmissionsController < ApplicationController
skip_before_action :verify_authenticity_token
@@ -14,10 +16,14 @@ class SubmissionsController < ApplicationController
respond_to do |format|
if @submission.save
format.html { redirect_to(@form.thank_you_url) if @form.thank_you_url.present? }
format.html do
redirect_to(@form.thank_you_url) if @form.thank_you_url.present?
end
format.json { render(json: { success: true, submission: @submission.data }) }
else
format.html { redirect_to(@form.thank_you_url) if @form.thank_you_url.present? }
format.html do
redirect_to(@form.thank_you_url) if @form.thank_you_url.present?
end
format.json { render(json: { error: @submission.errors }, status: 422) }
end
end
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module ApplicationHelper
include Pagy::Frontend
end
-7
View File
@@ -1,7 +0,0 @@
document.addEventListener("turbolinks:load", function () {
document.querySelectorAll('pre code').forEach(function (block) {
if (window.hljs) {
hljs.highlightBlock(block);
}
});
});
+1 -1
View File
@@ -13,7 +13,7 @@ require('tinyforms');
require('demo');
require('backend_typed');
require('tabs');
require('highlight');
require('prism');
// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
+17
View File
@@ -0,0 +1,17 @@
window.Prism = window.Prism || {};
window.Prism.manual = true;
document.addEventListener("turbolinks:load", function () {
Prism.plugins.customClass.prefix('prism-');
document.querySelectorAll('pre code').forEach(function (el) {
Prism.highlightElement(el);
});
});
/* PrismJS 1.20.0
https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript&plugins=custom-class */
var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\blang(?:uage)?-([\w-]+)\b/i,n=0,C={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof _?new _(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++n}),e.__id},clone:function t(e,r){var a,n,l=C.util.type(e);switch(r=r||{},l){case"Object":if(n=C.util.objId(e),r[n])return r[n];for(var i in a={},r[n]=a,e)e.hasOwnProperty(i)&&(a[i]=t(e[i],r));return a;case"Array":return n=C.util.objId(e),r[n]?r[n]:(a=[],r[n]=a,e.forEach(function(e,n){a[n]=t(e,r)}),a);default:return e}},getLanguage:function(e){for(;e&&!c.test(e.className);)e=e.parentElement;return e?(e.className.match(c)||[,"none"])[1].toLowerCase():"none"},currentScript:function(){if("undefined"==typeof document)return null;if("currentScript"in document)return document.currentScript;try{throw new Error}catch(e){var n=(/at [^(\r\n]*\((.*):.+:.+\)$/i.exec(e.stack)||[])[1];if(n){var t=document.getElementsByTagName("script");for(var r in t)if(t[r].src==n)return t[r]}return null}}},languages:{extend:function(e,n){var t=C.util.clone(C.languages[e]);for(var r in n)t[r]=n[r];return t},insertBefore:function(t,e,n,r){var a=(r=r||C.languages)[t],l={};for(var i in a)if(a.hasOwnProperty(i)){if(i==e)for(var o in n)n.hasOwnProperty(o)&&(l[o]=n[o]);n.hasOwnProperty(i)||(l[i]=a[i])}var s=r[t];return r[t]=l,C.languages.DFS(C.languages,function(e,n){n===s&&e!=t&&(this[e]=l)}),l},DFS:function e(n,t,r,a){a=a||{};var l=C.util.objId;for(var i in n)if(n.hasOwnProperty(i)){t.call(n,i,n[i],r||i);var o=n[i],s=C.util.type(o);"Object"!==s||a[l(o)]?"Array"!==s||a[l(o)]||(a[l(o)]=!0,e(o,t,i,a)):(a[l(o)]=!0,e(o,t,null,a))}}},plugins:{},highlightAll:function(e,n){C.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var r={callback:t,container:e,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};C.hooks.run("before-highlightall",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),C.hooks.run("before-all-elements-highlight",r);for(var a,l=0;a=r.elements[l++];)C.highlightElement(a,!0===n,r.callback)},highlightElement:function(e,n,t){var r=C.util.getLanguage(e),a=C.languages[r];e.className=e.className.replace(c,"").replace(/\s+/g," ")+" language-"+r;var l=e.parentNode;l&&"pre"===l.nodeName.toLowerCase()&&(l.className=l.className.replace(c,"").replace(/\s+/g," ")+" language-"+r);var i={element:e,language:r,grammar:a,code:e.textContent};function o(e){i.highlightedCode=e,C.hooks.run("before-insert",i),i.element.innerHTML=i.highlightedCode,C.hooks.run("after-highlight",i),C.hooks.run("complete",i),t&&t.call(i.element)}if(C.hooks.run("before-sanity-check",i),!i.code)return C.hooks.run("complete",i),void(t&&t.call(i.element));if(C.hooks.run("before-highlight",i),i.grammar)if(n&&u.Worker){var s=new Worker(C.filename);s.onmessage=function(e){o(e.data)},s.postMessage(JSON.stringify({language:i.language,code:i.code,immediateClose:!0}))}else o(C.highlight(i.code,i.grammar,i.language));else o(C.util.encode(i.code))},highlight:function(e,n,t){var r={code:e,grammar:n,language:t};return C.hooks.run("before-tokenize",r),r.tokens=C.tokenize(r.code,r.grammar),C.hooks.run("after-tokenize",r),_.stringify(C.util.encode(r.tokens),r.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var r in t)n[r]=t[r];delete n.rest}var a=new l;return M(a,a.head,e),function e(n,t,r,a,l,i,o){for(var s in r)if(r.hasOwnProperty(s)&&r[s]){var u=r[s];u=Array.isArray(u)?u:[u];for(var c=0;c<u.length;++c){if(o&&o==s+","+c)return;var g=u[c],f=g.inside,h=!!g.lookbehind,d=!!g.greedy,v=0,p=g.alias;if(d&&!g.pattern.global){var m=g.pattern.toString().match(/[imsuy]*$/)[0];g.pattern=RegExp(g.pattern.source,m+"g")}g=g.pattern||g;for(var y=a.next,k=l;y!==t.tail;k+=y.value.length,y=y.next){var b=y.value;if(t.length>n.length)return;if(!(b instanceof _)){var x=1;if(d&&y!=t.tail.prev){g.lastIndex=k;var w=g.exec(n);if(!w)break;var A=w.index+(h&&w[1]?w[1].length:0),P=w.index+w[0].length,S=k;for(S+=y.value.length;S<=A;)y=y.next,S+=y.value.length;if(S-=y.value.length,k=S,y.value instanceof _)continue;for(var O=y;O!==t.tail&&(S<P||"string"==typeof O.value&&!O.prev.value.greedy);O=O.next)x++,S+=O.value.length;x--,b=n.slice(k,S),w.index-=k}else{g.lastIndex=0;var w=g.exec(b)}if(w){h&&(v=w[1]?w[1].length:0);var A=w.index+v,w=w[0].slice(v),P=A+w.length,E=b.slice(0,A),N=b.slice(P),j=y.prev;E&&(j=M(t,j,E),k+=E.length),W(t,j,x);var L=new _(s,f?C.tokenize(w,f):w,p,w,d);if(y=M(t,j,L),N&&M(t,y,N),1<x&&e(n,t,r,y.prev,k,!0,s+","+c),i)break}else if(i)break}}}}}(e,a,n,a.head,0),function(e){var n=[],t=e.head.nLine truncated
Prism.languages.markup={comment:/<!--[\s\S]*?-->/,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0},cdata:/<!\[CDATA\[[\s\S]*?]]>/i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/i,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/i,inside:{punctuation:[/^=/,{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&amp;/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^<!\[CDATA\[|\]\]>$/i;var n={"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:s}};n["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var t={};t[a]={pattern:RegExp("(<__[^]*?>)(?:<!\\[CDATA\\[(?:[^\\]]|\\](?!\\]>))*\\]\\]>|(?!<!\\[CDATA\\[)[^])*?(?=</__>)".replace(/__/g,function(){return a}),"i"),lookbehind:!0,greedy:!0,inside:n},Prism.languages.insertBefore("markup","cdata",t)}}),Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup;
!function(s){var e=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+[\s\S]*?(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\((?!\s*\))\s*)(?:[^()]|\((?:[^()]|\([^()]*\))*\))+?(?=\s*\))/,lookbehind:!0,alias:"selector"}}},url:{pattern:RegExp("url\\((?:"+e.source+"|[^\n\r()]*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/}},selector:RegExp("[^{}\\s](?:[^{};\"']|"+e.source+")*?(?=\\s*\\{)"),string:{pattern:e,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),s.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:t.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:s.languages.css}},alias:"language-css"}},t.tag))}(Prism);
Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,function:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,operator:/--|\+\+|\*\*=?|=>|&&|\|\||[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?[.?]?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0},"function-variable":{pattern:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/i,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.js=Prism.languages.javascript;
!function(){if("undefined"!=typeof self&&self.Prism||"undefined"!=typeof global&&global.Prism){var a,e,t="";Prism.plugins.customClass={add:function(n){a=n},map:function(s){e="function"==typeof s?s:function(n){return s[n]||n}},prefix:function(n){t=n||""}},Prism.hooks.add("wrap",function(s){if(a){var n=a({content:s.content,type:s.type,language:s.language});Array.isArray(n)?s.classes.push.apply(s.classes,n):n&&s.classes.push(n)}(e||t)&&(s.classes=s.classes.map(function(n){return t+(e?e(n,s.language):n)}))})}}();
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked
+4 -2
View File
@@ -1,8 +1,10 @@
# frozen_string_literal: true
class SubmissionAppendJob < ApplicationJob
queue_as :default
rescue_from(Signet::AuthorizationError, Google::Apis::AuthorizationError) do |exception|
submission_id = self.arguments.first
rescue_from(Signet::AuthorizationError, Google::Apis::AuthorizationError) do |_exception|
submission_id = arguments.first
Rails.logger.error("AuthorizationError during SubmissionAppend: submission_id=#{submission_id}")
submission = Submission.find(submission_id)
submission.form.deactivate!('AuthorizationError')
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class ApplicationMailer < ActionMailer::Base
default from: 'from@example.com'
layout 'mailer'
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
+16 -9
View File
@@ -1,7 +1,9 @@
# frozen_string_literal: true
class Authentication < ApplicationRecord
belongs_to :user
scope :for, -> (provider) { where(provider: provider) }
scope :for, ->(provider) { where(provider: provider) }
encrypts :access_token
encrypts :refresh_token
@@ -10,19 +12,24 @@ class Authentication < ApplicationRecord
expires_at <= Time.current
end
def refresh_from(client_secret)
client_secret.refresh!
self.access_token = client_secret.access_token if client_secret.access_token.present?
self.refresh_token = client_secret.refresh_token if client_secret.refresh_token.present?
self.expires_at = Time.zone.at(client_secret.expires_at) if client_secret.expires_at.present?
save
end
def google_authorization
return nil unless provider == 'google'
@google_authorization ||= CLIENT_SECRETS.to_authorization.tap do |c|
c.access_token = self.access_token
c.refresh_token = self.refresh_token
c.expires_at = self.expires_at
c.access_token = access_token
c.refresh_token = refresh_token
c.expires_at = expires_at
if expires_at < 1.minute.from_now
c.refresh!
self.access_token = c.access_token if c.access_token.present?
self.refresh_token = c.refresh_token if c.refresh_token.present?
self.expires_at = Time.at(c.expires_at) if c.expires_at.present?
refresh_from(c)
end
end
end
end
+9 -9
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class Form < ApplicationRecord
belongs_to :user
has_many :submissions, dependent: :destroy
@@ -10,12 +12,12 @@ class Form < ApplicationRecord
encrypts :airtable_api_key
encrypts :airtable_app_key
validates_presence_of :title
validates_inclusion_of :backend_name, in: ['google_sheets', 'airtable']
validates :title, presence: true
validates :backend_name, inclusion: { in: %w[google_sheets airtable] }
# Airtable validations
validates_presence_of :airtable_api_key, if: :airtable?
validates_presence_of :airtable_app_key, if: :airtable?
validates_presence_of :airtable_table, if: :airtable?
validates :airtable_api_key, presence: { if: :airtable? }
validates :airtable_app_key, presence: { if: :airtable? }
validates :airtable_table, presence: { if: :airtable? }
# TODO: use counter_cache option on association
def submissions_count
@@ -27,12 +29,10 @@ class Form < ApplicationRecord
end
def deactivate!(reason = nil)
self.user.deactivate!(reason)
user.deactivate!(reason)
end
def active?
self.user.active?
end
delegate :active?, to: :user
def airtable?
backend_name == 'airtable'
+6 -3
View File
@@ -1,10 +1,12 @@
# frozen_string_literal: true
class Submission < ApplicationRecord
belongs_to :form
has_many_attached :files
acts_as_sequenced scope: :form_id
validates_presence_of :data, if: :appended_at?
validates :data, presence: { if: :appended_at? }
def process_data(submitted_data)
processed_data = {}
@@ -12,7 +14,7 @@ class Submission < ApplicationRecord
processed_data[key] = submission_value_for(value)
end
update_attribute(:data, processed_data)
SubmissionAppendJob.perform_later(self.id)
SubmissionAppendJob.perform_later(id)
end
def submission_value_for(value)
@@ -37,7 +39,8 @@ class Submission < ApplicationRecord
attachment = ActiveStorage::Attachment.new(record: self, name: 'files', blob: create_one.blob)
attachment.save
# return the URL that we use to show in the Spreadsheet
Rails.application.routes.url_helpers.file_upload_url(form_id: form, submission_id: self, id: attachment.token, host: DEFAULT_HOST, filename: attachment.blob.filename)
Rails.application.routes.url_helpers.file_upload_url(form_id: form, submission_id: self, id: attachment.token,
host: DEFAULT_HOST, filename: attachment.blob.filename)
else
value.to_s
end
+3 -1
View File
@@ -1,9 +1,11 @@
# frozen_string_literal: true
class User < ApplicationRecord
authenticates_with_sorcery!
has_many :authentications, dependent: :destroy
has_many :forms, dependent: :destroy
def deactivate!(reason = nil)
def deactivate!(_reason = nil)
# currently we only use deactivate if we get an authentication exception appending data to a spreadsheet
authentications.last&.update(expires_at: Time.current)
end
+10
View File
@@ -6,6 +6,16 @@
<%= text_field_tag header %>
</p>
<% end %>
<% if form.spreadsheet_headers.blank? %>
<p>
<label for="Name">Name</label>
<input type="text" name="Name" required>
</p>
<p>
<label for="Email">Email</label>
<input type="email" name="Email" required>
</p>
<% end %>
...
<input type="submit" value="Submit">
</form>
@@ -7,5 +7,4 @@
</p>
<% end %>
...
<input type="submit" value="Submit">
</form>
+2 -5
View File
@@ -42,7 +42,7 @@
<p>
Please set your form action as in the example below. Make sure to use proper <code>name</code> attributes.
<p>
<pre><code class="html">
<pre><code class="language-html">
<%= html_escape_once render('form_example', form: @form) %>
</code></pre>
@@ -50,12 +50,9 @@
<p>
Learn more about our <a href="https://www.notion.so/JavaScript-Library-442071548edd4cd1af9c7d8edb3d42cb">JavaScript library</a> and our <a href="https://www.notion.so/Tinyforms-API-7862355e22ce4bbead5de3b635cda55e">API</a> in your help section.
</p>
<pre><code class="html">
<pre><code class="language-html">
<%= html_escape_once render('form_example_js', form: @form) %>
</code></pre>
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.0.0/build/styles/railscasts.min.css">
<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.0.0/build/highlight.min.js"></script>
</div>
</div>
+10 -9
View File
@@ -24,22 +24,23 @@
<div class="columns is-centered">
<div class="column is-two-thirds">
<h3 class="title">Setup your online form:</h3>
<h3 class="title">One more step:</h3>
<p>
Your form submission URL: <code><%= submission_url(@form) %></code>
</p>
<p>
Set the <code>action</code> of your online form to this URL and make sure your fields have proper <code>name</code> attributes.
Set the <code>action</code> of your online form to <code><%= submission_url(@form) %></code> and make sure your fields have proper <code>name</code> attributes.
</p>
<h4 class="subtitle" style="margin-top:1em">Example:</h4>
<pre><code class="html">
<pre><code class="language-html">
<%= html_escape_once render('form_example', form: @form) %>
</code></pre>
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.0.0/build/styles/default.min.css">
<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.0.0/build/highlight.min.js"></script>
<p>
Let us know if you need <%= link_to 'help', help_url %> - we also build the form for you!
If you prefer to submit the form using JavaScript/AJAX have a look at our <a href="https://www.notion.so/JavaScript-Library-442071548edd4cd1af9c7d8edb3d42cb">JavaScript library</a> and our <a href="https://www.notion.so/Tinyforms-API-7862355e22ce4bbead5de3b635cda55e">API</a>.
</p>
<p>
If you need help, <%= link_to "we're here!", help_url %>
</p>
<p style="margin-top:15px">
And if you do not want to deal with your form at all, have a look at our <%= link_to 'form building service', form_building_service_url %>.
</p>
</div>
+1 -1
View File
@@ -14,7 +14,7 @@
<% end %>
<hr>
<p>
Simply login with your existing Google account.
Login with your existing Google account, no new credentials needed.
<br>
Do you need help? <%= link_to 'let us know', help_url %>.
</p>