feat: add CopyableTextWidget and <wutta-copyable-text> component
This commit is contained in:
parent
49c001c9ad
commit
70950ae9b8
3 changed files with 117 additions and 0 deletions
|
|
@ -147,6 +147,24 @@ class NotesWidget(TextAreaWidget):
|
|||
readonly_template = "readonly/notes"
|
||||
|
||||
|
||||
class CopyableTextWidget(Widget): # pylint: disable=abstract-method
|
||||
"""
|
||||
A readonly text widget which adds a "copy" icon/link just after
|
||||
the text.
|
||||
"""
|
||||
|
||||
def serialize(self, field, cstruct, **kw): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
if not cstruct:
|
||||
return colander.null
|
||||
|
||||
return HTML.tag("wutta-copyable-text", **{"text": cstruct})
|
||||
|
||||
def deserialize(self, field, pstruct): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class WuttaCheckboxChoiceWidget(CheckboxChoiceWidget):
|
||||
"""
|
||||
Custom widget for :class:`python:set` fields.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
${self.make_wutta_autocomplete_component()}
|
||||
${self.make_wutta_button_component()}
|
||||
${self.make_wutta_checked_password_component()}
|
||||
${self.make_wutta_copyable_text_component()}
|
||||
${self.make_wutta_datepicker_component()}
|
||||
${self.make_wutta_timepicker_component()}
|
||||
${self.make_wutta_filter_component()}
|
||||
|
|
@ -349,6 +350,72 @@
|
|||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="make_wutta_copyable_text_component()">
|
||||
<script type="text/x-template" id="wutta-copyable-text-template">
|
||||
<span>
|
||||
|
||||
<span v-if="!iconFirst">{{ text }}</span>
|
||||
|
||||
<b-tooltip label="Copied!" :triggers="['click']">
|
||||
<a v-if="text"
|
||||
href="#"
|
||||
@click.prevent="copyText()">
|
||||
<b-icon icon="copy" pack="fas" />
|
||||
</a>
|
||||
</b-tooltip>
|
||||
|
||||
<span v-if="iconFirst">{{ text }}</span>
|
||||
|
||||
## dummy input field needed to copy text on *insecure* sites
|
||||
<b-input v-model="legacyText" ref="legacyText" v-show="legacyText" />
|
||||
|
||||
</span>
|
||||
</script>
|
||||
<script>
|
||||
const WuttaCopyableText = {
|
||||
template: '#wutta-copyable-text-template',
|
||||
props: {
|
||||
text: {required: true},
|
||||
iconFirst: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
## dummy input value needed to copy text on *insecure* sites
|
||||
legacyText: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
async copyText() {
|
||||
|
||||
if (navigator.clipboard) {
|
||||
// this is the way forward, but requires HTTPS
|
||||
navigator.clipboard.writeText(this.text)
|
||||
|
||||
} else {
|
||||
|
||||
// use deprecated 'copy' command, but this just
|
||||
// tells the browser to copy currently-selected
|
||||
// text..which means we first must "add" some text
|
||||
// to screen, and auto-select that, before copying
|
||||
// to clipboard
|
||||
this.legacyText = this.text
|
||||
this.$nextTick(() => {
|
||||
let input = this.$refs.legacyText.$el.firstChild
|
||||
input.select()
|
||||
document.execCommand('copy')
|
||||
// re-hide the dummy input
|
||||
this.legacyText = null
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
Vue.component('wutta-copyable-text', WuttaCopyableText)
|
||||
<% request.register_component('wutta-copyable-text', 'WuttaCopyableText') %>
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="make_wutta_datepicker_component()">
|
||||
<script type="text/x-template" id="wutta-datepicker-template">
|
||||
<b-datepicker :name="name"
|
||||
|
|
|
|||
|
|
@ -95,6 +95,38 @@ class TestObjectRefWidget(WebTestCase):
|
|||
self.assertNotIn("url", values)
|
||||
|
||||
|
||||
class TestCopyableTextWidget(WebTestCase):
|
||||
|
||||
def make_field(self, node, **kwargs):
|
||||
# TODO: not sure why default renderer is in use even though
|
||||
# pyramid_deform was included in setup? but this works..
|
||||
kwargs.setdefault("renderer", deform.Form.default_renderer)
|
||||
return deform.Field(node, **kwargs)
|
||||
|
||||
def make_widget(self, **kwargs):
|
||||
return mod.CopyableTextWidget(**kwargs)
|
||||
|
||||
def test_serialize(self):
|
||||
node = colander.SchemaNode(colander.String())
|
||||
field = self.make_field(node)
|
||||
widget = self.make_widget()
|
||||
|
||||
self.assertIs(widget.serialize(field, colander.null), colander.null)
|
||||
self.assertIs(widget.serialize(field, None), colander.null)
|
||||
self.assertIs(widget.serialize(field, ""), colander.null)
|
||||
|
||||
result = widget.serialize(field, "hello world")
|
||||
self.assertEqual(
|
||||
result, '<wutta-copyable-text text="hello world"></wutta-copyable-text>'
|
||||
)
|
||||
|
||||
def test_deserialize(self):
|
||||
node = colander.SchemaNode(colander.String())
|
||||
field = self.make_field(node)
|
||||
widget = self.make_widget()
|
||||
self.assertRaises(NotImplementedError, widget.deserialize, field, "hello world")
|
||||
|
||||
|
||||
class TestWuttaDateWidget(WebTestCase):
|
||||
|
||||
def make_field(self, node, **kwargs):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue