Write custom GraphQL explorer with optional JS
This commit is contained in:
parent
d87c17e417
commit
668356d5b0
|
@ -193,6 +193,14 @@ class SrhtFlask(Flask):
|
|||
"templates"
|
||||
)))
|
||||
|
||||
try:
|
||||
with open(os.path.join(path, "schema.graphqls")) as f:
|
||||
self.graphql_schema = f.read()
|
||||
with open(os.path.join(path, "default_query.graphql")) as f:
|
||||
self.graphql_query = f.read()
|
||||
except:
|
||||
pass
|
||||
|
||||
self.jinja_env.cache = None
|
||||
self.jinja_env.filters['date'] = datef
|
||||
self.jinja_env.globals['pagination'] = pagination
|
||||
|
@ -344,13 +352,6 @@ class SrhtFlask(Flask):
|
|||
).observe(max(default_timer() - request._srht_start_time, 0))
|
||||
return resp
|
||||
|
||||
from srht.oauth import loginrequired
|
||||
|
||||
@self.route("/graphql")
|
||||
@loginrequired
|
||||
def query_explorer():
|
||||
return render_template("graphql.html")
|
||||
|
||||
def make_response(self, rv):
|
||||
# Converts responses from dicts to JSON response objects
|
||||
response = None
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
"""
|
||||
pygments.lexers.graphql
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
Lexers for GraphQL formats.
|
||||
:copyright: Copyright 2017 by Martin Zlámal.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from pygments.lexer import RegexLexer
|
||||
from pygments.token import *
|
||||
|
||||
class GraphqlLexer(RegexLexer):
|
||||
"""
|
||||
Lexer for GraphQL.
|
||||
"""
|
||||
|
||||
name = 'GraphQL'
|
||||
aliases = ['graphql', 'gql']
|
||||
filenames = ['*.graphql', '*.gql']
|
||||
mimetypes = ['application/graphql']
|
||||
|
||||
tokens = {
|
||||
'root': [
|
||||
(r'#.*', Comment.Singline),
|
||||
(r'\.\.\.', Operator),
|
||||
(r'(-?0|-?[1-9][0-9]*)(\.[0-9]+[eE][+-]?[0-9]+|\.[0-9]+|[eE][+-]?[0-9]+)', Number.Float),
|
||||
(r'(-?0|-?[1-9][0-9]*)', Number.Integer),
|
||||
(r'\$+[_A-Za-z][_0-9A-Za-z]*', Name.Variable),
|
||||
(r'[_A-Za-z][_0-9A-Za-z]+\s?:', Text),
|
||||
(r'(type|query|mutation|@[a-z]+|on|true|false|null)\b', Keyword.Type),
|
||||
(r'[!$():=@\[\]{|}]+?', Punctuation),
|
||||
(r'[_A-Za-z][_0-9A-Za-z]*', Keyword),
|
||||
(r'(\s|,)', Text),
|
||||
]
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import json
|
||||
import pygments
|
||||
import requests
|
||||
from flask import Blueprint, current_app, render_template, request, abort
|
||||
from jinja2 import Markup
|
||||
from pygments import highlight
|
||||
from pygments.formatters import HtmlFormatter
|
||||
from pygments.lexers import JsonLexer
|
||||
from srht.gql_lexer import GraphqlLexer
|
||||
from srht.oauth import loginrequired
|
||||
from srht.validation import Validation
|
||||
|
||||
gql_blueprint = Blueprint('srht.graphql', __name__)
|
||||
|
||||
_schema_html = None
|
||||
|
||||
def execute_gql(query):
|
||||
r = requests.post("http://meta.sr.ht.local/query",
|
||||
cookies=request.cookies,
|
||||
headers={"Content-Type": "application/json"},
|
||||
json={"query": query})
|
||||
j = json.dumps(r.json(), indent=2)
|
||||
lexer = JsonLexer()
|
||||
formatter = HtmlFormatter()
|
||||
style = formatter.get_style_defs('.highlight')
|
||||
html = (f"<style>{style}</style>"
|
||||
+ highlight(j, lexer, formatter))
|
||||
return Markup(html)
|
||||
|
||||
@gql_blueprint.route("/graphql")
|
||||
@loginrequired
|
||||
def query_explorer():
|
||||
schema = current_app.graphql_schema
|
||||
query = current_app.graphql_query
|
||||
global _schema_html
|
||||
if _schema_html is None:
|
||||
lexer = GraphqlLexer()
|
||||
formatter = HtmlFormatter()
|
||||
style = formatter.get_style_defs('.highlight')
|
||||
_schema_html = (f"<style>{style}</style>"
|
||||
+ highlight(schema, lexer, formatter))
|
||||
_schema_html = Markup(_schema_html)
|
||||
results = execute_gql(query)
|
||||
return render_template("graphql.html",
|
||||
schema=_schema_html, query=query, results=results)
|
||||
|
||||
@gql_blueprint.route("/graphql", methods=["POST"])
|
||||
@loginrequired
|
||||
def query_explorer_POST():
|
||||
schema = current_app.graphql_schema
|
||||
valid = Validation(request)
|
||||
query = valid.require("query")
|
||||
if not valid.ok:
|
||||
abort(400)
|
||||
global _schema_html
|
||||
if _schema_html is None:
|
||||
lexer = GraphqlLexer()
|
||||
formatter = HtmlFormatter()
|
||||
style = formatter.get_style_defs('.highlight')
|
||||
_schema_html = (f"<style>{style}</style>"
|
||||
+ highlight(schema, lexer, formatter))
|
||||
_schema_html = Markup(_schema_html)
|
||||
results = execute_gql(query)
|
||||
return render_template("graphql.html",
|
||||
schema=_schema_html, query=query, results=results)
|
|
@ -0,0 +1,349 @@
|
|||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
color: black;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
.CodeMirror pre.CodeMirror-line,
|
||||
.CodeMirror pre.CodeMirror-line-like {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
background-color: white; /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #f7f7f7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.CodeMirror-linenumbers {}
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.CodeMirror-guttermarker { color: black; }
|
||||
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.cm-fat-cursor .CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0 !important;
|
||||
background: #7e7;
|
||||
}
|
||||
.cm-fat-cursor div.CodeMirror-cursors {
|
||||
z-index: 1;
|
||||
}
|
||||
.cm-fat-cursor-mark {
|
||||
background-color: rgba(20, 255, 20, 0.5);
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
}
|
||||
.cm-animate-fat-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
background-color: #7e7;
|
||||
}
|
||||
@-moz-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@-webkit-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||
|
||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||
|
||||
.CodeMirror-rulers {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: -50px; bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
top: 0; bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-header {color: blue;}
|
||||
.cm-s-default .cm-quote {color: #090;}
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
.cm-strikethrough {text-decoration: line-through;}
|
||||
|
||||
.cm-s-default .cm-keyword {color: #708;}
|
||||
.cm-s-default .cm-atom {color: #219;}
|
||||
.cm-s-default .cm-number {color: #164;}
|
||||
.cm-s-default .cm-def {color: #00f;}
|
||||
.cm-s-default .cm-variable,
|
||||
.cm-s-default .cm-punctuation,
|
||||
.cm-s-default .cm-property,
|
||||
.cm-s-default .cm-operator {}
|
||||
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
|
||||
.cm-s-default .cm-comment {color: #a50;}
|
||||
.cm-s-default .cm-string {color: #a11;}
|
||||
.cm-s-default .cm-string-2 {color: #f50;}
|
||||
.cm-s-default .cm-meta {color: #555;}
|
||||
.cm-s-default .cm-qualifier {color: #555;}
|
||||
.cm-s-default .cm-builtin {color: #30a;}
|
||||
.cm-s-default .cm-bracket {color: #997;}
|
||||
.cm-s-default .cm-tag {color: #170;}
|
||||
.cm-s-default .cm-attribute {color: #00c;}
|
||||
.cm-s-default .cm-hr {color: #999;}
|
||||
.cm-s-default .cm-link {color: #00c;}
|
||||
|
||||
.cm-s-default .cm-error {color: #f00;}
|
||||
.cm-invalidchar {color: #f00;}
|
||||
|
||||
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||
|
||||
/* Default styles for common addons */
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
|
||||
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||
|
||||
/* STOP */
|
||||
|
||||
/* The rest of this file contains styles related to the mechanics of
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: scroll !important; /* Things will break if this is overridden */
|
||||
/* 50px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -50px; margin-right: -50px;
|
||||
padding-bottom: 50px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
border-right: 50px solid transparent;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actual scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
}
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0; top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0; left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0; bottom: 0;
|
||||
}
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0; bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
min-height: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: -50px;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.CodeMirror-gutter-background {
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
min-height: 1px; /* prevents collapsing before first draw */
|
||||
}
|
||||
.CodeMirror pre.CodeMirror-line,
|
||||
.CodeMirror pre.CodeMirror-line-like {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: contextual;
|
||||
font-variant-ligatures: contextual;
|
||||
}
|
||||
.CodeMirror-wrap pre.CodeMirror-line,
|
||||
.CodeMirror-wrap pre.CodeMirror-line-like {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 0.1px; /* Force widget margins to stay inside of the container */
|
||||
}
|
||||
|
||||
.CodeMirror-widget {}
|
||||
|
||||
.CodeMirror-rtl pre { direction: rtl; }
|
||||
|
||||
.CodeMirror-code {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Force content-box sizing for the elements where we expect it */
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-sizer,
|
||||
.CodeMirror-gutter,
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-linenumber {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
div.CodeMirror-dragcursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-focused div.CodeMirror-cursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
.CodeMirror-crosshair { cursor: crosshair; }
|
||||
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||
|
||||
.cm-searching {
|
||||
background-color: #ffa;
|
||||
background-color: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* Used to force a border model for a node */
|
||||
.cm-force-border { padding-right: .1px; }
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* See issue #2901 */
|
||||
.cm-tab-wrap-hack:after { content: ''; }
|
||||
|
||||
/* Help users use markselection to safely style text background */
|
||||
span.CodeMirror-selectedtext { background: none; }
|
File diff suppressed because it is too large
Load Diff
|
@ -1,2 +0,0 @@
|
|||
body{margin:0;padding:0;font-family:sans-serif;overflow:hidden}#root{height:100%}body{font-family:Open Sans,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:rgba(0,0,0,.8);line-height:1.5;height:100vh;letter-spacing:.53px;margin-right:-1px!important}a,body,code,h1,h2,h3,h4,html,p,pre,ul{margin:0;padding:0;color:inherit}a:active,a:focus,button:focus,input:focus{outline:none}button,input,submit{border:none}button,input,pre{font-family:Open Sans,sans-serif}code{font-family:Consolas,monospace}
|
||||
/*# sourceMappingURL=index.css.map*/
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,216 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineSimpleMode = function(name, states) {
|
||||
CodeMirror.defineMode(name, function(config) {
|
||||
return CodeMirror.simpleMode(config, states);
|
||||
});
|
||||
};
|
||||
|
||||
CodeMirror.simpleMode = function(config, states) {
|
||||
ensureState(states, "start");
|
||||
var states_ = {}, meta = states.meta || {}, hasIndentation = false;
|
||||
for (var state in states) if (state != meta && states.hasOwnProperty(state)) {
|
||||
var list = states_[state] = [], orig = states[state];
|
||||
for (var i = 0; i < orig.length; i++) {
|
||||
var data = orig[i];
|
||||
list.push(new Rule(data, states));
|
||||
if (data.indent || data.dedent) hasIndentation = true;
|
||||
}
|
||||
}
|
||||
var mode = {
|
||||
startState: function() {
|
||||
return {state: "start", pending: null,
|
||||
local: null, localState: null,
|
||||
indent: hasIndentation ? [] : null};
|
||||
},
|
||||
copyState: function(state) {
|
||||
var s = {state: state.state, pending: state.pending,
|
||||
local: state.local, localState: null,
|
||||
indent: state.indent && state.indent.slice(0)};
|
||||
if (state.localState)
|
||||
s.localState = CodeMirror.copyState(state.local.mode, state.localState);
|
||||
if (state.stack)
|
||||
s.stack = state.stack.slice(0);
|
||||
for (var pers = state.persistentStates; pers; pers = pers.next)
|
||||
s.persistentStates = {mode: pers.mode,
|
||||
spec: pers.spec,
|
||||
state: pers.state == state.localState ? s.localState : CodeMirror.copyState(pers.mode, pers.state),
|
||||
next: s.persistentStates};
|
||||
return s;
|
||||
},
|
||||
token: tokenFunction(states_, config),
|
||||
innerMode: function(state) { return state.local && {mode: state.local.mode, state: state.localState}; },
|
||||
indent: indentFunction(states_, meta)
|
||||
};
|
||||
if (meta) for (var prop in meta) if (meta.hasOwnProperty(prop))
|
||||
mode[prop] = meta[prop];
|
||||
return mode;
|
||||
};
|
||||
|
||||
function ensureState(states, name) {
|
||||
if (!states.hasOwnProperty(name))
|
||||
throw new Error("Undefined state " + name + " in simple mode");
|
||||
}
|
||||
|
||||
function toRegex(val, caret) {
|
||||
if (!val) return /(?:)/;
|
||||
var flags = "";
|
||||
if (val instanceof RegExp) {
|
||||
if (val.ignoreCase) flags = "i";
|
||||
val = val.source;
|
||||
} else {
|
||||
val = String(val);
|
||||
}
|
||||
return new RegExp((caret === false ? "" : "^") + "(?:" + val + ")", flags);
|
||||
}
|
||||
|
||||
function asToken(val) {
|
||||
if (!val) return null;
|
||||
if (val.apply) return val
|
||||
if (typeof val == "string") return val.replace(/\./g, " ");
|
||||
var result = [];
|
||||
for (var i = 0; i < val.length; i++)
|
||||
result.push(val[i] && val[i].replace(/\./g, " "));
|
||||
return result;
|
||||
}
|
||||
|
||||
function Rule(data, states) {
|
||||
if (data.next || data.push) ensureState(states, data.next || data.push);
|
||||
this.regex = toRegex(data.regex);
|
||||
this.token = asToken(data.token);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
function tokenFunction(states, config) {
|
||||
return function(stream, state) {
|
||||
if (state.pending) {
|
||||
var pend = state.pending.shift();
|
||||
if (state.pending.length == 0) state.pending = null;
|
||||
stream.pos += pend.text.length;
|
||||
return pend.token;
|
||||
}
|
||||
|
||||
if (state.local) {
|
||||
if (state.local.end && stream.match(state.local.end)) {
|
||||
var tok = state.local.endToken || null;
|
||||
state.local = state.localState = null;
|
||||
return tok;
|
||||
} else {
|
||||
var tok = state.local.mode.token(stream, state.localState), m;
|
||||
if (state.local.endScan && (m = state.local.endScan.exec(stream.current())))
|
||||
stream.pos = stream.start + m.index;
|
||||
return tok;
|
||||
}
|
||||
}
|
||||
|
||||
var curState = states[state.state];
|
||||
for (var i = 0; i < curState.length; i++) {
|
||||
var rule = curState[i];
|
||||
var matches = (!rule.data.sol || stream.sol()) && stream.match(rule.regex);
|
||||
if (matches) {
|
||||
if (rule.data.next) {
|
||||
state.state = rule.data.next;
|
||||
} else if (rule.data.push) {
|
||||
(state.stack || (state.stack = [])).push(state.state);
|
||||
state.state = rule.data.push;
|
||||
} else if (rule.data.pop && state.stack && state.stack.length) {
|
||||
state.state = state.stack.pop();
|
||||
}
|
||||
|
||||
if (rule.data.mode)
|
||||
enterLocalMode(config, state, rule.data.mode, rule.token);
|
||||
if (rule.data.indent)
|
||||
state.indent.push(stream.indentation() + config.indentUnit);
|
||||
if (rule.data.dedent)
|
||||
state.indent.pop();
|
||||
var token = rule.token
|
||||
if (token && token.apply) token = token(matches)
|
||||
if (matches.length > 2 && rule.token && typeof rule.token != "string") {
|
||||
state.pending = [];
|
||||
for (var j = 2; j < matches.length; j++)
|
||||
if (matches[j])
|
||||
state.pending.push({text: matches[j], token: rule.token[j - 1]});
|
||||
stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0));
|
||||
return token[0];
|
||||
} else if (token && token.join) {
|
||||
return token[0];
|
||||
} else {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
||||
stream.next();
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
function cmp(a, b) {
|
||||
if (a === b) return true;
|
||||
if (!a || typeof a != "object" || !b || typeof b != "object") return false;
|
||||
var props = 0;
|
||||
for (var prop in a) if (a.hasOwnProperty(prop)) {
|
||||
if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) return false;
|
||||
props++;
|
||||
}
|
||||
for (var prop in b) if (b.hasOwnProperty(prop)) props--;
|
||||
return props == 0;
|
||||
}
|
||||
|
||||
function enterLocalMode(config, state, spec, token) {
|
||||
var pers;
|
||||
if (spec.persistent) for (var p = state.persistentStates; p && !pers; p = p.next)
|
||||
if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode == p.mode) pers = p;
|
||||
var mode = pers ? pers.mode : spec.mode || CodeMirror.getMode(config, spec.spec);
|
||||
var lState = pers ? pers.state : CodeMirror.startState(mode);
|
||||
if (spec.persistent && !pers)
|
||||
state.persistentStates = {mode: mode, spec: spec.spec, state: lState, next: state.persistentStates};
|
||||
|
||||
state.localState = lState;
|
||||
state.local = {mode: mode,
|
||||
end: spec.end && toRegex(spec.end),
|
||||
endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false),
|
||||
endToken: token && token.join ? token[token.length - 1] : token};
|
||||
}
|
||||
|
||||
function indexOf(val, arr) {
|
||||
for (var i = 0; i < arr.length; i++) if (arr[i] === val) return true;
|
||||
}
|
||||
|
||||
function indentFunction(states, meta) {
|
||||
return function(state, textAfter, line) {
|
||||
if (state.local && state.local.mode.indent)
|
||||
return state.local.mode.indent(state.localState, textAfter, line);
|
||||
if (state.indent == null || state.local || meta.dontIndentStates && indexOf(state.state, meta.dontIndentStates) > -1)
|
||||
return CodeMirror.Pass;
|
||||
|
||||
var pos = state.indent.length - 1, rules = states[state.state];
|
||||
scan: for (;;) {
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
var rule = rules[i];
|
||||
if (rule.data.dedent && rule.data.dedentIfLineStart !== false) {
|
||||
var m = rule.regex.exec(textAfter);
|
||||
if (m && m[0]) {
|
||||
pos--;
|
||||
if (rule.next || rule.push) rules = states[rule.next || rule.push];
|
||||
textAfter = textAfter.slice(m[0].length);
|
||||
continue scan;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return pos < 0 ? 0 : state.indent[pos];
|
||||
};
|
||||
}
|
||||
});
|
|
@ -1,134 +1,83 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset=utf-8/>
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
|
||||
<link rel="stylesheet" href="/static/graphql.css">
|
||||
<script src="/static/graphql.js"></script>
|
||||
<title>GraphQL API Explorer</title>
|
||||
</head>
|
||||
<body>
|
||||
<style type="text/css">
|
||||
html { font-family: "Open Sans", sans-serif; overflow: hidden; }
|
||||
body { margin: 0; background: #172a3a; }
|
||||
noscript { display: block; text-align: center; color: white; }
|
||||
</style>
|
||||
<noscript>
|
||||
Sorry, this tool requires JavaScript to be enabled.
|
||||
</noscript>
|
||||
<div id="root"/>
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('load', function (event) {
|
||||
const root = document.getElementById('root');
|
||||
root.classList.add('playgroundIn');
|
||||
const wsProto = location.protocol == 'https:' ? 'wss:' : 'ws:'
|
||||
GraphQLPlayground.init(root, {
|
||||
endpoint: location.protocol + '//' + location.host + '/query',
|
||||
settings: {
|
||||
'request.credentials': 'same-origin'
|
||||
},
|
||||
tabs: [
|
||||
{
|
||||
name: "List repositories",
|
||||
endpoint: location.protocol + '//' + location.host + '/query',
|
||||
query: `# Welcome to the SourceHut GraphQL explorer
|
||||
# You can use this to run test requests against the GraphQL API
|
||||
# To browse the GraphQL schema, use the "Schema" button on the far right.
|
||||
# There's a sample query for you here; and more samples in other tabs.
|
||||
query {
|
||||
# Fetch info about the authenticated user (you):
|
||||
me {
|
||||
# Grab their canonical name:
|
||||
canonicalName
|
||||
# And a list of repositories:
|
||||
repositories(filter: { count: 5 }) {
|
||||
# This resource is paginated, so it has a cursor. If you pass this value
|
||||
# into repositories(cursor:"...") in a subsequent request, you'll get the
|
||||
# next page.
|
||||
cursor
|
||||
# These are the actual results. Grab the id, name, and updated fields
|
||||
# from each repository.
|
||||
results {
|
||||
id, name, updated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Also fetch the API version. Please note that the GraphQL API is considered
|
||||
# experimental: as long as this returns 0.0.0, the API is subject to change
|
||||
# without notice. Some features may not be working; notably, all write
|
||||
# operations are presently unsupported.
|
||||
version {
|
||||
major, minor, patch
|
||||
}
|
||||
|
||||
# On this page, you have been automatically authorized to make API requests
|
||||
# with your sr.ht login cookie. If you wish to make GraphQL requests outside
|
||||
# of the browser, create a personal access token at https://meta.sr.ht/oauth
|
||||
#
|
||||
# curl \\
|
||||
# -H Authorization:"Bearer <your oauth token>" \\
|
||||
# -H Content-Type:application/json \\
|
||||
# -d '{"query": "{ me { canonicalName } }"}' \\
|
||||
# https://git.sr.ht/query
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "Get latest commit",
|
||||
endpoint: location.protocol + '//' + location.host + '/query',
|
||||
query: `query {
|
||||
me {
|
||||
repositories(filter: { count: 5 }) {
|
||||
results {
|
||||
name
|
||||
HEAD {
|
||||
name
|
||||
follow {
|
||||
... on Commit {
|
||||
id
|
||||
message
|
||||
author { name, email }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{% extends "layout.html" %}
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="/static/codemirror.css">
|
||||
<style>
|
||||
.CodeMirror {
|
||||
border: 1px solid #eee;
|
||||
height: 35rem;
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Get build manifests",
|
||||
endpoint: location.protocol + '//' + location.host + '/query',
|
||||
query: `query {
|
||||
me {
|
||||
repositories(filter: { count: 5 }) {
|
||||
results {
|
||||
multiple: path(path:".builds") {
|
||||
object {
|
||||
... on Tree {
|
||||
entries {
|
||||
results {
|
||||
name
|
||||
object { ... on TextBlob { text } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
single: path(path:".build.yml") {
|
||||
object {
|
||||
... on TextBlob { text }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<form class="container" id="query-form" method="POST">
|
||||
{{csrf_token()}}
|
||||
<noscript class="alert alert-info d-block">
|
||||
<strong>Notice:</strong> This page works without JavaScript, but the
|
||||
experience is improved if you enable it.
|
||||
</noscript>
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<textarea
|
||||
class="form-control"
|
||||
rows="25"
|
||||
id="editor"
|
||||
placeholder="Enter a GraphQL query here"
|
||||
name="query">{{query}}</textarea>
|
||||
<script>
|
||||
/* Reduce effects of FOUC for JS users */
|
||||
document.getElementById('editor').style.display = 'none';
|
||||
</script>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
{{results}}
|
||||
<button class="btn btn-primary pull-right" type="submit">
|
||||
Submit query {{icon('caret-right')}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<details style="margin-top: 1rem">
|
||||
<summary>View GraphQL schema</summary>
|
||||
{{schema}}
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="/static/codemirror.js"></script>
|
||||
<script src="/static/simple.js"></script>
|
||||
<script>
|
||||
CodeMirror.defineSimpleMode("graphql", {
|
||||
start: [
|
||||
{regex: /"(?:[^\\]|\\.)*?(?:"|$)/, token: "string"},
|
||||
{regex: /#.*/, token: "comment"},
|
||||
{regex: /\w[a-zA-Z]+/, token: "atom"},
|
||||
],
|
||||
meta: {
|
||||
lineComment: "#"
|
||||
}
|
||||
}`
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
const el = document.getElementById('editor');
|
||||
let cm = CodeMirror(elt => {
|
||||
el.parentNode.replaceChild(elt, el);
|
||||
}, {
|
||||
value: el.value,
|
||||
mode: 'graphql',
|
||||
lineNumbers: true,
|
||||
});
|
||||
|
||||
document.querySelector('button[type="submit"]').addEventListener('click', ev => {
|
||||
ev.preventDefault();
|
||||
let form = document.getElementById('query-form');
|
||||
let node = document.createElement('input');
|
||||
node.type = 'hidden';
|
||||
node.name = 'query';
|
||||
node.value = cm.getValue();
|
||||
form.appendChild(node);
|
||||
form.submit();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in New Issue