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
|
||||
# Copyright © 2010-2021 Lance Edgar
|
||||
# Copyright © 2010-2022 Lance Edgar
|
||||
#
|
||||
# This file is part of Rattail.
|
||||
#
|
||||
|
@ -40,6 +40,7 @@ from webhelpers2.html.tags import *
|
|||
|
||||
from tailbone.util import (csrf_token, get_csrf_token,
|
||||
pretty_datetime, raw_datetime,
|
||||
render_markdown,
|
||||
route_exists)
|
||||
|
||||
|
||||
|
|
|
@ -112,6 +112,19 @@ header .level .theme-picker {
|
|||
margin-top: 1em;
|
||||
}
|
||||
|
||||
/******************************
|
||||
* markdown
|
||||
******************************/
|
||||
|
||||
.rendered-markdown p,
|
||||
.rendered-markdown ul {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.rendered-markdown .codehilite {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/******************************
|
||||
* fix datepicker within modals
|
||||
* TODO: someday this may not be necessary? cf.
|
||||
|
|
|
@ -5,21 +5,6 @@
|
|||
|
||||
<%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()">
|
||||
|
||||
<b-field horizontal label="App Prefix"
|
||||
|
@ -290,7 +275,7 @@
|
|||
</p>
|
||||
</header>
|
||||
<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>
|
||||
|
||||
|
|
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 name="base_meta" file="/base_meta.mako" />
|
||||
<%namespace file="/formposter.mako" import="declare_formposter_mixin" />
|
||||
<%namespace name="page_help" file="/page_help.mako" />
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
@ -383,17 +384,9 @@
|
|||
</div>
|
||||
% endif
|
||||
|
||||
## Help Button
|
||||
% if help_url is not Undefined and help_url:
|
||||
<div class="level-item">
|
||||
<b-button tag="a" href="${help_url}"
|
||||
target="_blank"
|
||||
icon-pack="fas"
|
||||
icon-left="fas fa-question-circle">
|
||||
Help
|
||||
</b-button>
|
||||
<page-help></page-help>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
## Feedback Button / Dialog
|
||||
% if request.has_perm('common.feedback'):
|
||||
|
@ -466,6 +459,8 @@
|
|||
</div>
|
||||
</script>
|
||||
|
||||
${page_help.render_template()}
|
||||
|
||||
<script type="text/x-template" id="feedback-template">
|
||||
<div>
|
||||
|
||||
|
@ -736,6 +731,7 @@
|
|||
</%def>
|
||||
|
||||
<%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__))}
|
||||
<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__))}
|
||||
|
||||
${page_help.make_component()}
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
FeedbackForm.data = function() { return FeedbackFormData }
|
||||
|
|
|
@ -33,6 +33,8 @@ import pytz
|
|||
import humanize
|
||||
import logging
|
||||
|
||||
import markdown
|
||||
|
||||
from rattail.time import timezone, make_utc
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
Set the app theme. This modifies the *global* Mako template lookup
|
||||
|
|
|
@ -287,6 +287,8 @@ class CommonView(View):
|
|||
|
||||
# permissions
|
||||
config.add_tailbone_permission_group('common', "(common)", overwrite=False)
|
||||
config.add_tailbone_permission('common', 'common.edit_help',
|
||||
"Edit help info for *any* page")
|
||||
|
||||
# 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
|
||||
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:
|
||||
return self.help_url
|
||||
|
||||
|
@ -2275,6 +2285,54 @@ class MasterView(View):
|
|||
|
||||
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):
|
||||
"""
|
||||
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,
|
||||
'grid_index': self.grid_index,
|
||||
'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,
|
||||
}
|
||||
|
||||
|
@ -4761,6 +4822,17 @@ class MasterView(View):
|
|||
'prevent_cache_for_index_views',
|
||||
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
|
||||
if cls.listable:
|
||||
config.add_tailbone_permission(permission_prefix, '{}.list'.format(permission_prefix),
|
||||
|
|
Loading…
Reference in a new issue