Wagtail Draftail: Add class to div - editor

I want to add a custom element to my draftail editor. Adding a div element was not a problem. But I also want to add to the div my own class.
How can I do this? This is my current code:
#hooks.register('register_rich_text_features')
def register_infobox_feature(features):
"""
Registering the `mark` feature, which uses the `MARK` Draft.js inline style type,
and is stored as HTML with a `<mark>` tag.
"""
feature_name = 'infobox'
type_ = 'div'
tag = 'div'
# 2. Configure how Draftail handles the feature in its toolbar.
control = {
'type': type_,
'label': 'InfoBox',
'description': 'Infobox',
# This isn’t even required – Draftail has predefined styles for MARK.
# 'style': {'textDecoration': 'line-through'},
}
# 3. Call register_editor_plugin to register the configuration for Draftail.
features.register_editor_plugin(
'draftail', feature_name, draftail_features.InlineStyleFeature(control)
)
# 4.configure the content transform from the DB to the editor and back.
db_conversion = {
'from_database_format': {tag: InlineStyleElementHandler(type_)},
'to_database_format': {'style_map': {type_: tag, 'props': {'class' : 'test'}}},
}
# 5. Call register_converter_rule to register the content transformation conversion.
features.register_converter_rule('contentstate', feature_name, db_conversion)
# 6. (optional) Add the feature to the default features list to make it available
# on rich text fields that do not specify an explicit 'features' list
features.default_features.append('infobox')
It looks like that it is possible for new block elements but not for entities. Why?
https://docs.wagtail.io/en/v2.6/advanced_topics/customisation/extending_draftail.html#creating-new-inline-styles

Had the same problem today, I could not find solution in any documentation, but I simply added:
tag = 'div class="myClass"'
and it worked.
I am posting whole funtion I have just in case someone would look for such solution:
import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.admin.rich_text.converters.html_to_contentstate import InlineStyleElementHandler
from wagtail.core import hooks
# 1. Use the register_rich_text_features hook.
#hooks.register('register_rich_text_features')
def register_flashgreen_feature(features):
"""
Registering the `mark` feature, which uses the `MARK` Draft.js inline style type,
and is stored as HTML with a `<mark>` tag.
"""
feature_name = 'FlashGreen'
type_ = 'FG'
tag = 'div class="FG"'
# 2. Configure how Draftail handles the feature in its toolbar.
control = {
'type': type_,
'label': 'Flash_Green',
'description': 'Flash Green',
'style': {'color': '#00F000'},
}
# 3. Call register_editor_plugin to register the configuration for Draftail.
features.register_editor_plugin(
'draftail', feature_name, draftail_features.InlineStyleFeature(control)
)
# 4.configure the content transform from the DB to the editor and back.
db_conversion = {
'from_database_format': {tag: InlineStyleElementHandler(type_)},
'to_database_format': {'style_map': {type_: tag}},
}
# 5. Call register_converter_rule to register the content transformation conversion.
features.register_converter_rule(
'contentstate', feature_name, db_conversion)
# 6. (optional) Add the feature to the default features list to make it available
# on rich text fields that do not specify an explicit 'features' list
features.default_features.append('FlashGreen')

Related

Override Python Eve Endpoint because of backend column changes?

How do I override an endpoint if I want to run some data transformations before it hit's the database. For e.g., let's say we had a people table, with a column name fname and we renamed it to first_name. But our users are making queries with fname. Is there a way of overriding the endpoint with a custom route for people so that I can transform the column name from fname to first_name or more complexly, run some python code before either calling SQLALchemy by myself or perhaps returning it back to the Eve framework to continue with calling the database?
E.g. using the QuickStart, I tried something like this but it didn't work:
from eve import Eve
from flask import jsonify
app = Eve()
#app.route('/people/<name>')
def custom_people_func(name):
return jsonify(name='Override', people_name=name)
if __name__ == '__main__':
app.run()
settings.py
people = {
# 'title' tag used in item links. Defaults to the resource title minus
# the final, plural 's' (works fine in most cases but not for 'people')
'item_title': 'person',
# by default the standard item entry point is defined as
# '/people/<ObjectId>'. We leave it untouched, and we also enable an
# additional read-only entry point. This way consumers can also perform
# GET requests at '/people/<lastname>'.
'additional_lookup': {
'url': 'regex("[\w]+")',
'field': 'lastname'
},
# We choose to override global cache-control directives for this resource.
'cache_control': 'max-age=10,must-revalidate',
'cache_expires': 10,
# most global settings can be overridden at resource level
'resource_methods': ['GET', 'POST'],
'schema': schema
}
DOMAIN = {'people': people}
When I do curl -i http://127.0.0.1:5000/people/obama, it won't call the method I defined but the default Eve routing.
Asking generally, how do we manage these kind of database changes, if possible, using Eve?
Have you looked into Event Hooks, specifically into database event hooks? They allow you to hook a callout function to your db events (insert, replace, delete, fetch.) Within your function you can, for example, change the incoming payload before it hits the database.
>>> def before_insert(resource_name, items):
... print('About to store items to "%s" ' % resource)
... # modify incoming items here
>>> app = Eve()
>>> app.on_insert += before_insert
>>> app.run()

How to use LaTeX section numbers in Pandoc cross-reference

The Pandoc documentation says that cross references can be made to section headers in a number of ways. For example, you can create your own ID and reference that ID. For example:
# This is my header {#header}
Will create an ID with value '#header' that can be refenced in the text, as such:
[Link to header](#header)
Which will display the text 'Link to header' with a link to the header.
I couldn't find anywhere how to make the text of the link be the section number when compiled as a LaTeX document.
For example, if my header is compiled to '1.2.3 Section Header', I want my cross-reference to text to display as '1.2.3'.
This can be achieved by defining the ID as done previously. eg:
# This is my header {#header}
Then in the text, the cross reference can be written as:
\ref{header}
When this compiles to LaTeX, the cross-reference text will be the section number of the referenced heading.
You can use the pandoc-secnos filter, which is part of the pandoc-xnos filter suite.
The header
# This is my header {#sec:header}
is referenced using #sec:header. Alternatively, you can reference
# This is my header
using #sec:this-is-my-header.
Markdown documents coded in this way can be processed by adding --filter pandoc-secnos to the pandoc call. The --number-sections option should be used as well. The output uses LaTeX's native commands (i.e., \label and \ref or \cref).
The benefit to this approach is that output in other formats (html, epub, docx, ...) is also possible.
A general solution which works with all supported output formats can be build by leveraging pandoc Lua filters: The function pandoc.utils.hierarchicalize can be used to get the document hierarchy. We can use this to associate section IDs with section numbers, which can later be used to add these numbers to links with no link description (e.g., [](#myheader)).
local hierarchicalize = (require 'pandoc.utils').hierarchicalize
local section_numbers = {}
function populate_section_numbers (doc)
function populate (elements)
for _, el in pairs(elements) do
if el.t == 'Sec' then
section_numbers['#' .. el.attr.identifier] = table.concat(el.numbering, '.')
populate(el.contents)
end
end
end
populate(hierarchicalize(doc.blocks))
end
function resolve_section_ref (link)
if #link.content > 0 or link.target:sub(1, 1) ~= '#' then
return nil
end
local section_number = pandoc.Str(section_numbers[link.target])
return pandoc.Link({section_number}, link.target, link.title, link.attr)
end
return {
{Pandoc = populate_section_numbers},
{Link = resolve_section_ref}
}
The above should be saved to a file and then passed to pandoc via the --lua-filter option.
Example
Using the example from the question
# This is my header {#header}
## Some subsection
See section [](#header), especially [](#some-subsection)
Using the above filter, the last line will render as "See section 1, especially 1.1".
Don't forget to call pandoc with option --number-sections, or headers will not be numbered.
Since pandoc version 2.8 the function pandoc.utils.hierarchicalize has been replaced with make_sections. Here is an updated version of the #tarleb's answer which works with newer ´pandoc´ versions.
local make_sections = (require 'pandoc.utils').make_sections
local section_numbers = {}
function populate_section_numbers (doc)
function populate (elements)
for _, el in pairs(elements) do
if el.t == 'Div' and el.attributes.number then
section_numbers['#' .. el.attr.identifier] = el.attributes.number
populate(el.content)
end
end
end
populate(make_sections(true, nil, doc.blocks))
end
function resolve_section_ref (link)
if #link.content > 0 or link.target:sub(1, 1) ~= '#' then
return nil
end
local section_number = pandoc.Str(section_numbers[link.target])
return pandoc.Link({section_number}, link.target, link.title, link.attr)
end
return {
{Pandoc = populate_section_numbers},
{Link = resolve_section_ref}
}

How can I set Algolia searchableAttributes on an instantsearch widget?

I am trying to set searchable attributes so that these can by dynamically controlled by locale. I am attempting to follow this guide from algolia on multi lang support:
https://www.algolia.com/doc/guides/search/multilingual-search/
The example shows setting this value on index:
Algolia.init_index('movies').set_settings({"searchableAttributes"=>["title_eng,title_fr,title_es"]})
but this is not how I am creating my index, maybe I am missing something? I also don't appear to have the set_settings method on the helper.
I am trying to set this via:
helper.setQueryParameter('searchableAttributes', searchable_terms_array)
found towards the bottom of the following coffee script code block
searchable_terms_array = [
'title_de'
'title_en'
'title_fr'
]
restricted_terms_array = [
'title_' + current_locale
]
search = instantsearch(
appId: 'MY-ID'
apiKey: 'MY_KEY'
indexName: 'my_index_' + rails_env
urlSync: {
threshold: 300
getHistoryState: ->
{ turbolinks: true }
}
searchFunction: (helper) ->
query = search.helper.state.query
# Here is my attempt, doesn't seem to work
helper.setQueryParameter('searchableAttributes', searchable_terms_array)
# is there another way to set above line?
helper.setQueryParameter('restrictSearchableAttributes', restricted_terms_array)
videos.helper.setQuery query
videos.helper.search()
helper.search()
return
)
Finally, it may be important to note that I am setting the primary searchable attributes via the Algolia admin console, but assume I am supposed to be setting the additional language related fields to searchable via the API.
searchableAttributes is setting not a query parameter. The JS Helper is a query only layer on top of the client. This means that you can't set the settings of your index using the JS Helper.
What you need to do for multiple language support is to create a replicas per language. Each replica will have a different set of searchable attributes. Then using instantsearch.js or the JS Helper you can switch indices, respectively using the sortBySelector widget or the setIndex method of the helper.

Rails Microsoft Word, XML databinding, repeat rows

Those willing to jump straight to my questions can go to the paragraph "Please help with". You will find there my beginning of implementation, along with short XML samples
The story
The famous problem of inserting repeating content, like table rows, into a word template, using the rails framework.
I decided to implement a 'cleaner' solution for replacing some variables in a Word document with rails, using XML databinding. This solution works very well for non-repetitive content, but for repetitive content, a little extra dirty work must be done and I need help with it.
No C#, No Visual, just plain olde ruby on rails & XML
The databinded document
I have a Word document with some content controls, tagged with "human-readable" text, so my users know what should be inside.
I have used Word 2007 Content Control Toolkit to add some custom XML to a .docx file. Therefore in each .docx I have some customXml/itemsx.xml that contains my custom XML.
I have manually databinded this XML to text content control I have in my word template, using drag & drop with Word 2007 Content Control Toolkit.
The replacing process with nokogiri
Basically I already have some code that replaces every XML node by the corresponding value from a hash. For example if I provide this hash to my function :
variables = {
"some_xml-node" => "some_value"
}
It will properly replace XML in customXml/itemsx.xml of .docx file :
<root> <some> <xml-node>some_value</xml-node></some> </root>
So this is taken care of !
The repetitive content
Now as I said, this works perfectly for non-repetitive content. For repetitive content (in my case I want to repeat some <w:tr> in a document), the solution I'd like to go with, is
Manually insert some tags in word/document.xml of .docx file (this is dirty, but hell I can't think of anything else) before every <tr> that needs to be duplicated
In rails, parse the XML and locate the <tr> that needs duplicating using Nokogiri
Copy the tr as many times as I need
Look at some text inside this <tr>, find the databinding (which looks like <w:dataBinding w:xpath="/root[1]/movies[1]/movie[1]/name[1]"
Replace movie[1] by movie[index]
Repeat for every table that needs <tr> duplication
With this solution Therefore I ensure 100% compatibility with my existing system ! It's some kind of preprocessing...
Please help with
Finding an XML comment containing a custom string, and selecting the node just below it (using Nokogiri)
Changing attributes in many sub-nodes of the node found in 1.
XML/Hash samples that could be used (my beginning of implementation after that):
Sample of .docx word/document.xml
<w:document>
<!-- My_Custom_Tag_ID -->
<w:tr someparam="something">
<w:td></w:td>
<w:td><w:sthelse></w:sthelse><w:dataBinding w:xpath="/root[1]/movies[1]/movie[1]/name[1]><w:sth>Value</w:sth></w:td>
<w:td></<:td>
</w:tr>
</w:document>
Sample of input parameter repeat_tag hash
repeat_tags_sample = [
{
"tag" => "My_Custom_Tag_ID",
"repeatable-content" => "movie"
},
{
"tag" => "My_Custom_Tag_ID_2",
"repeatable-content" => "cartoons"
}
]
Sample of input parameter contents hash
contents_sample =
{
"movies" => [{"name" => "X-Men",
"year" => 1998,
"property-xxx" => 42
}, { "name" => "X-Men-4",
"year" => 2007,
"property-xxx" => 42
}],
"cartoons" => [{"name" => "Tom_Jerry",
"year" => 1995,
"property-yyy" => "cat"
}, { "name" => "Random_name",
"year" => 2008,
"property-yyy" => 42
}]
}
My beginning of implementation :
def dynamic_table_content(zip, repeat_tags, contents)
doc = zip.find_entry("word/document.xml")
xml = Nokogiri::XML.parse(doc.get_input_dtream)
# repeat_tags_sample = [ {
# "tag" => My_Custom_Tag_ID",
# "repeatable-content" => "movie"},
# ...]
repeat_tags.each do |rpt|
content = contents[rpt[:repeatable-content]]
# content now looks like [
# {"name" => "X-Men",
# "year" => 1998,
# "property-xxx" => 42, ...},
# ...]
content_name = rpt[:repeateable_content].to_s
# the 'movie' of '/root[1]/movies[1]/movie[1]/name[1]' (see below)
puts "Processing #{rpt[:tag]}, adding #{content_name}s"
# Word document.xml sample code looks like this :
# <!-- My_Custom_Tag_ID_inserted_manually -->
# <w:tr ...>
# ...
# <w:dataBinding w:xpath="/root[1]/movies[1]/movie[1]/name[1]>
# ...
# </w:tr>
Find a comment containing a custom string, and select the node just below
# Find starting <w:tr > tag located after <!-- rpt[:tag] -->
base_tr_node = find the node after
# Duplicate it as many times as we want.
content.each_with_index do |content, index|
puts "Adding #{content_name} : #{content}.to_s"
new_tr_node = base_tr_node.add_next_sibling(base_tr_node)
# inside this new node there are many
# <w:dataBinding w:xpath="/root[1]/movies[1]/movie[1]/name[1]>
# <w:dataBinding w:xpath="/root[1]/movies[1]/movie[1]/year[1]>
# ..../movie[1]/property-xxx[1]
# GOAL : replace every movie[1] by movie[index]
Change attributes in many sub-nodes of the node found in 1.
new_tr_node.change_attributes as shown in (see GOAL in previous comments)
# Maybe, it would be something like
# new_tr_node.gsub("(#{content_name})\[([1-9]+)\]", "\1\[#{index}\]")
# ... But new_tr_node is a nokogiri element so .gsub doesn't exist
end
end
#replace["word/document.xml"] = xml.serialize :save_zip_with => 0
end
I have looked at the DoPE extension for Word documents. It looks great ! But alas I had already done a lot of work, and just now I (almost) finished building my own preprocessor.
What I needed was more complicated than what I originally asked. But nevertheless, the answers would be :
EDIT : fixed bad regex/xpath
# 1. Find a comment containing a custom string, and select the node just below
comment_nodes = doc.xpath("//comment()")
# Loop like comment_nodes.each do |comment|
base_tr_node = comment.next_sibling.next_sibling
# For some reason, need to apply next_sibling twice, thought the comment is indeed just above the <w:tr> node
# 2. Change attributes in many sub-nodes of the node found in 1.
matches = tr_node.search('.//*[name()='w:dataBinding']')
matches.each do |databinding_node|
# replace '.*phase[1].*' by '.*phase[index].*'
databinding_node['w:xpath'].gsub("#{comment.text}\[1\]", "#{comment.text}\[#{index}\]")
end

Rails checkboxes/form based on YML/DB

I want to create a big checkbox area with maybe about 30 checkboxes. I want, instead of hard-writing each checkbox, to populate this from the db, or from the translation yml. How would I create this loop to do so from a yml? I'm guessing that's bad practice? If so, how to loop for db values?
An example of check boxes of Colors:
From YAML:
# controller
def show
#options = YAML.load(File.read(Rails.root.join('db', 'fixtures', 'checkboxes.yml')))
# view
Check the colors you want:
- #options.each do |option|
= option
= check_box_tag 'colors[]', option
# example of received parameters:
params = { colors: [ 'red', 'blue' ] }
# db/fixtures/checkboxes.yml
---
- Blue
- Red
- White
- Black
- Green
- Yellow
This is a static list of pre-defined Colors which means you would need to manually edit the YML file AND restart your server to see the changes on this list.
Is it a disadvantage ? Yes and no, depends on what you want to do.
From DB:
# controller
def show
#options = Color.all.map{ |color| [color.name, color.id] }
# view
Check the colors you want:
- #options.each do |option_name, value|
= option_name
= check_box_tag 'colors[]', value
# example of received parameters:
params = { colors: [1,2,3] } # ids of Color records
This is a dynamic list of Colors that can be maintained easily via the web interface (no need to restart the server). You can do the basic CRUD (create retrieve update delete) actions on these, whereas the YAML file cannot be handled that easy (possibility to re-write and re-load the file on the fly but WAOW such a pain in the ass in comparison to the "Rails' way" with a Color model.
loaded from I18n's translation file:
# implying that en.posts.colors is a list of color names
en:
# ...
posts:
# ...
colors:
black: Black
red: Red
white: White
# view
- t('posts.colors').each do |i18n_key, color_name|
= color_name
= check_box_tag 'colors[]', color_name
# example of received parameters:
params = { colors: [ 'red', 'blue' ] }
How to decide?
It depends on what you need:
You need the list to be static? (classic usage: countries, languages, colors, genders, etc.) -> Go for the YAML option
You need the list to be dynamic? (classic usage: users, categories, content that can be edited inside the app) -> Go for the Model option

Resources