Add basic support for editing page help info
site admin should be able to point help wherever they want
This commit is contained in:
parent
ed54092268
commit
9fe9983bf9
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2021 Lance Edgar
|
# Copyright © 2010-2022 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -40,6 +40,7 @@ from webhelpers2.html.tags import *
|
||||||
|
|
||||||
from tailbone.util import (csrf_token, get_csrf_token,
|
from tailbone.util import (csrf_token, get_csrf_token,
|
||||||
pretty_datetime, raw_datetime,
|
pretty_datetime, raw_datetime,
|
||||||
|
render_markdown,
|
||||||
route_exists)
|
route_exists)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -112,6 +112,19 @@ header .level .theme-picker {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/******************************
|
||||||
|
* markdown
|
||||||
|
******************************/
|
||||||
|
|
||||||
|
.rendered-markdown p,
|
||||||
|
.rendered-markdown ul {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rendered-markdown .codehilite {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
/******************************
|
/******************************
|
||||||
* fix datepicker within modals
|
* fix datepicker within modals
|
||||||
* TODO: someday this may not be necessary? cf.
|
* TODO: someday this may not be necessary? cf.
|
||||||
|
|
|
@ -5,21 +5,6 @@
|
||||||
|
|
||||||
<%def name="content_title()"></%def>
|
<%def name="content_title()"></%def>
|
||||||
|
|
||||||
<%def name="extra_styles()">
|
|
||||||
${parent.extra_styles()}
|
|
||||||
<style type="text/css">
|
|
||||||
|
|
||||||
.content.result p {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content.result .codehilite {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="page_content()">
|
<%def name="page_content()">
|
||||||
|
|
||||||
<b-field horizontal label="App Prefix"
|
<b-field horizontal label="App Prefix"
|
||||||
|
@ -290,7 +275,7 @@
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="content result">${rendered_result or ""|n}</div>
|
<div class="content result rendered-markdown">${rendered_result or ""|n}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
204
tailbone/templates/page_help.mako
Normal file
204
tailbone/templates/page_help.mako
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
|
||||||
|
<%def name="render_template()">
|
||||||
|
<script type="text/x-template" id="page-help-template">
|
||||||
|
<div>
|
||||||
|
|
||||||
|
% if help_url or help_markdown:
|
||||||
|
|
||||||
|
<b-field>
|
||||||
|
<p class="control">
|
||||||
|
<b-button icon-pack="fas"
|
||||||
|
icon-left="question-circle"
|
||||||
|
% if help_markdown:
|
||||||
|
@click="displayInit()"
|
||||||
|
% elif help_url:
|
||||||
|
tag="a" href="${help_url}"
|
||||||
|
target="_blank"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
Help
|
||||||
|
</b-button>
|
||||||
|
</p>
|
||||||
|
% if can_edit_help:
|
||||||
|
<p class="control">
|
||||||
|
<b-button @click="configureInit()">
|
||||||
|
<span><i class="fa fa-cog"></i></span>
|
||||||
|
</b-button>
|
||||||
|
</p>
|
||||||
|
% endif
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
% elif can_edit_help:
|
||||||
|
|
||||||
|
<b-field>
|
||||||
|
<p class="control">
|
||||||
|
<b-button @click="configureInit()">
|
||||||
|
<span><i class="fa fa-question-circle"></i></span>
|
||||||
|
<span><i class="fa fa-cog"></i></span>
|
||||||
|
</b-button>
|
||||||
|
</p>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% if help_markdown:
|
||||||
|
<b-modal has-modal-card
|
||||||
|
:active.sync="displayShowDialog">
|
||||||
|
<div class="modal-card">
|
||||||
|
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">${index_title}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="modal-card-body">
|
||||||
|
${h.render_markdown(help_markdown)}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
% if help_url:
|
||||||
|
<b-button type="is-primary"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="external-link-alt"
|
||||||
|
tag="a" href="${help_url}"
|
||||||
|
target="_blank">
|
||||||
|
More Info
|
||||||
|
</b-button>
|
||||||
|
% endif
|
||||||
|
<b-button @click="displayShowDialog = false">
|
||||||
|
Close
|
||||||
|
</b-button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% if can_edit_help:
|
||||||
|
|
||||||
|
<b-modal has-modal-card
|
||||||
|
:active.sync="configureShowDialog">
|
||||||
|
<div class="modal-card">
|
||||||
|
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">Configure Help</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="modal-card-body">
|
||||||
|
|
||||||
|
<p class="block">
|
||||||
|
This help info applies to all views with the current
|
||||||
|
route prefix.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<b-field grouped>
|
||||||
|
|
||||||
|
<b-field label="Route Prefix">
|
||||||
|
<span>${route_prefix}</span>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="URL Prefix">
|
||||||
|
<span>${master.get_url_prefix()}</span>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Help Link (URL)">
|
||||||
|
<b-input v-model="helpURL"
|
||||||
|
ref="helpURL">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Markdown Text">
|
||||||
|
<b-input v-model="markdownText"
|
||||||
|
type="textarea" rows="8">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<b-button @click="configureShowDialog = false">
|
||||||
|
Cancel
|
||||||
|
</b-button>
|
||||||
|
<b-button type="is-primary"
|
||||||
|
@click="configureSave()"
|
||||||
|
:disabled="configureSaving"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="save">
|
||||||
|
{{ configureSaving ? "Working, please wait..." : "Save" }}
|
||||||
|
</b-button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
|
||||||
|
% endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="declare_vars()">
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
let PageHelp = {
|
||||||
|
|
||||||
|
template: '#page-help-template',
|
||||||
|
mixins: [FormPosterMixin],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
displayInit() {
|
||||||
|
this.displayShowDialog = true
|
||||||
|
},
|
||||||
|
|
||||||
|
configureInit() {
|
||||||
|
this.configureShowDialog = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.helpURL.focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
% if can_edit_help:
|
||||||
|
configureSave() {
|
||||||
|
this.configureSaving = true
|
||||||
|
let url = '${url('{}.edit_help'.format(route_prefix))}'
|
||||||
|
let params = {
|
||||||
|
help_url: this.helpURL,
|
||||||
|
markdown_text: this.markdownText,
|
||||||
|
}
|
||||||
|
this.submitForm(url, params, response => {
|
||||||
|
this.configureShowDialog = false
|
||||||
|
this.$buefy.toast.open({
|
||||||
|
message: "Info was saved; please refresh page to see changes.",
|
||||||
|
type: 'is-info',
|
||||||
|
duration: 4000, // 4 seconds
|
||||||
|
})
|
||||||
|
this.configureSaving = false
|
||||||
|
}, response => {
|
||||||
|
this.configureSaving = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
% endif
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
let PageHelpData = {
|
||||||
|
displayShowDialog: false,
|
||||||
|
configureShowDialog: false,
|
||||||
|
configureSaving: false,
|
||||||
|
helpURL: ${json.dumps(help_url or None)|n},
|
||||||
|
markdownText: ${json.dumps(help_markdown or None)|n},
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="make_component()">
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
PageHelp.data = function() { return PageHelpData }
|
||||||
|
|
||||||
|
Vue.component('page-help', PageHelp)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
|
@ -3,6 +3,7 @@
|
||||||
<%namespace file="/autocomplete.mako" import="tailbone_autocomplete_template" />
|
<%namespace file="/autocomplete.mako" import="tailbone_autocomplete_template" />
|
||||||
<%namespace name="base_meta" file="/base_meta.mako" />
|
<%namespace name="base_meta" file="/base_meta.mako" />
|
||||||
<%namespace file="/formposter.mako" import="declare_formposter_mixin" />
|
<%namespace file="/formposter.mako" import="declare_formposter_mixin" />
|
||||||
|
<%namespace name="page_help" file="/page_help.mako" />
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
@ -383,17 +384,9 @@
|
||||||
</div>
|
</div>
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
## Help Button
|
<div class="level-item">
|
||||||
% if help_url is not Undefined and help_url:
|
<page-help></page-help>
|
||||||
<div class="level-item">
|
</div>
|
||||||
<b-button tag="a" href="${help_url}"
|
|
||||||
target="_blank"
|
|
||||||
icon-pack="fas"
|
|
||||||
icon-left="fas fa-question-circle">
|
|
||||||
Help
|
|
||||||
</b-button>
|
|
||||||
</div>
|
|
||||||
% endif
|
|
||||||
|
|
||||||
## Feedback Button / Dialog
|
## Feedback Button / Dialog
|
||||||
% if request.has_perm('common.feedback'):
|
% if request.has_perm('common.feedback'):
|
||||||
|
@ -466,6 +459,8 @@
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
${page_help.render_template()}
|
||||||
|
|
||||||
<script type="text/x-template" id="feedback-template">
|
<script type="text/x-template" id="feedback-template">
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
|
@ -736,6 +731,7 @@
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="declare_whole_page_vars()">
|
<%def name="declare_whole_page_vars()">
|
||||||
|
${page_help.declare_vars()}
|
||||||
${h.javascript_link(request.static_url('tailbone:static/themes/falafel/js/tailbone.feedback.js') + '?ver={}'.format(tailbone.__version__))}
|
${h.javascript_link(request.static_url('tailbone:static/themes/falafel/js/tailbone.feedback.js') + '?ver={}'.format(tailbone.__version__))}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
@ -811,6 +807,8 @@
|
||||||
|
|
||||||
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.autocomplete.js') + '?ver={}'.format(tailbone.__version__))}
|
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.autocomplete.js') + '?ver={}'.format(tailbone.__version__))}
|
||||||
|
|
||||||
|
${page_help.make_component()}
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
FeedbackForm.data = function() { return FeedbackFormData }
|
FeedbackForm.data = function() { return FeedbackFormData }
|
||||||
|
|
|
@ -33,6 +33,8 @@ import pytz
|
||||||
import humanize
|
import humanize
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import markdown
|
||||||
|
|
||||||
from rattail.time import timezone, make_utc
|
from rattail.time import timezone, make_utc
|
||||||
from rattail.files import resource_path
|
from rattail.files import resource_path
|
||||||
|
|
||||||
|
@ -181,6 +183,16 @@ def raw_datetime(config, value, verbose=False, as_date=False):
|
||||||
return HTML.tag('span', **kwargs)
|
return HTML.tag('span', **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def render_markdown(text, **kwargs):
|
||||||
|
"""
|
||||||
|
Render the given markdown text as HTML.
|
||||||
|
"""
|
||||||
|
kwargs.setdefault('extensions', ['fenced_code', 'codehilite'])
|
||||||
|
md = markdown.markdown(text, **kwargs)
|
||||||
|
md = HTML.literal(md)
|
||||||
|
return HTML.tag('div', class_='rendered-markdown', c=[md])
|
||||||
|
|
||||||
|
|
||||||
def set_app_theme(request, theme, session=None):
|
def set_app_theme(request, theme, session=None):
|
||||||
"""
|
"""
|
||||||
Set the app theme. This modifies the *global* Mako template lookup
|
Set the app theme. This modifies the *global* Mako template lookup
|
||||||
|
|
|
@ -287,6 +287,8 @@ class CommonView(View):
|
||||||
|
|
||||||
# permissions
|
# permissions
|
||||||
config.add_tailbone_permission_group('common', "(common)", overwrite=False)
|
config.add_tailbone_permission_group('common', "(common)", overwrite=False)
|
||||||
|
config.add_tailbone_permission('common', 'common.edit_help',
|
||||||
|
"Edit help info for *any* page")
|
||||||
|
|
||||||
# home
|
# home
|
||||||
config.add_route('home', '/')
|
config.add_route('home', '/')
|
||||||
|
|
|
@ -2267,6 +2267,16 @@ class MasterView(View):
|
||||||
so if you like you can return a different help URL depending on which
|
so if you like you can return a different help URL depending on which
|
||||||
type of CRUD view is in effect, etc.
|
type of CRUD view is in effect, etc.
|
||||||
"""
|
"""
|
||||||
|
model = self.model
|
||||||
|
route_prefix = self.get_route_prefix()
|
||||||
|
|
||||||
|
# nb. self.Session may differ, so use tailbone.db.Session
|
||||||
|
info = Session.query(model.TailbonePageHelp)\
|
||||||
|
.filter(model.TailbonePageHelp.route_prefix == route_prefix)\
|
||||||
|
.first()
|
||||||
|
if info and info.help_url:
|
||||||
|
return info.help_url
|
||||||
|
|
||||||
if self.help_url:
|
if self.help_url:
|
||||||
return self.help_url
|
return self.help_url
|
||||||
|
|
||||||
|
@ -2275,6 +2285,54 @@ class MasterView(View):
|
||||||
|
|
||||||
return global_help_url(self.rattail_config)
|
return global_help_url(self.rattail_config)
|
||||||
|
|
||||||
|
def get_help_markdown(self):
|
||||||
|
"""
|
||||||
|
Return the markdown help text for current page, if defined.
|
||||||
|
"""
|
||||||
|
model = self.model
|
||||||
|
route_prefix = self.get_route_prefix()
|
||||||
|
|
||||||
|
# nb. self.Session may differ, so use tailbone.db.Session
|
||||||
|
info = Session.query(model.TailbonePageHelp)\
|
||||||
|
.filter(model.TailbonePageHelp.route_prefix == route_prefix)\
|
||||||
|
.first()
|
||||||
|
if info and info.markdown_text:
|
||||||
|
return info.markdown_text
|
||||||
|
|
||||||
|
def edit_help(self):
|
||||||
|
if (not self.has_perm('edit_help')
|
||||||
|
and not self.request.has_perm('common.edit_help')):
|
||||||
|
raise self.forbidden()
|
||||||
|
|
||||||
|
model = self.model
|
||||||
|
route_prefix = self.get_route_prefix()
|
||||||
|
schema = colander.Schema()
|
||||||
|
|
||||||
|
schema.add(colander.SchemaNode(colander.String(),
|
||||||
|
name='help_url',
|
||||||
|
missing=None))
|
||||||
|
|
||||||
|
schema.add(colander.SchemaNode(colander.String(),
|
||||||
|
name='markdown_text',
|
||||||
|
missing=None))
|
||||||
|
|
||||||
|
factory = self.get_form_factory()
|
||||||
|
form = factory(schema=schema, request=self.request)
|
||||||
|
if not form.validate(newstyle=True):
|
||||||
|
return {'error': "Form did not validate"}
|
||||||
|
|
||||||
|
# nb. self.Session may differ, so use tailbone.db.Session
|
||||||
|
info = Session.query(model.TailbonePageHelp)\
|
||||||
|
.filter(model.TailbonePageHelp.route_prefix == route_prefix)\
|
||||||
|
.first()
|
||||||
|
if not info:
|
||||||
|
info = model.TailbonePageHelp(route_prefix=route_prefix)
|
||||||
|
Session.add(info)
|
||||||
|
|
||||||
|
info.help_url = form.validated['help_url']
|
||||||
|
info.markdown_text = form.validated['markdown_text']
|
||||||
|
return {'ok': True}
|
||||||
|
|
||||||
def render_to_response(self, template, data, **kwargs):
|
def render_to_response(self, template, data, **kwargs):
|
||||||
"""
|
"""
|
||||||
Return a response with the given template rendered with the given data.
|
Return a response with the given template rendered with the given data.
|
||||||
|
@ -2296,6 +2354,9 @@ class MasterView(View):
|
||||||
'action_url': self.get_action_url,
|
'action_url': self.get_action_url,
|
||||||
'grid_index': self.grid_index,
|
'grid_index': self.grid_index,
|
||||||
'help_url': self.get_help_url(),
|
'help_url': self.get_help_url(),
|
||||||
|
'help_markdown': self.get_help_markdown(),
|
||||||
|
'can_edit_help': (self.has_perm('edit_help')
|
||||||
|
or self.request.has_perm('common.edit_help')),
|
||||||
'quickie': None,
|
'quickie': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4761,6 +4822,17 @@ class MasterView(View):
|
||||||
'prevent_cache_for_index_views',
|
'prevent_cache_for_index_views',
|
||||||
default=True)
|
default=True)
|
||||||
|
|
||||||
|
# edit help info
|
||||||
|
config.add_tailbone_permission(permission_prefix,
|
||||||
|
'{}.edit_help'.format(permission_prefix),
|
||||||
|
"Edit help info for {}".format(model_title_plural))
|
||||||
|
config.add_route('{}.edit_help'.format(route_prefix),
|
||||||
|
'{}/edit-help'.format(url_prefix),
|
||||||
|
request_method='POST')
|
||||||
|
config.add_view(cls, attr='edit_help',
|
||||||
|
route_name='{}.edit_help'.format(route_prefix),
|
||||||
|
renderer='json')
|
||||||
|
|
||||||
# list/search
|
# list/search
|
||||||
if cls.listable:
|
if cls.listable:
|
||||||
config.add_tailbone_permission(permission_prefix, '{}.list'.format(permission_prefix),
|
config.add_tailbone_permission(permission_prefix, '{}.list'.format(permission_prefix),
|
||||||
|
|
Loading…
Reference in a new issue