Repository files navigation

Ruby Portable Text

A ruby library to render Portable text

This gem is meant to be easy to use but is also highly configurable and extensible to match many use cases. By default, it can serialize Portable Text to HTML.

You can:

  • easily render default PortableText blocks in html without any configuration
  • create custom block types, mark_defs. Add them or replace existing ones.
  • create custom HTML serializers for each block type or mark def. Add them or replace existing ones.
  • customize each HTML node with custom attributes
  • create a new serializer

This is a very early release so please open issues if something doesn't work as intended.

Installation

gem install portable_text

Usage

See Rails usage for usage in rails

PortableText::Serializer takes 2 parameters:

  • content: , the portable text Array
  • to: , the rendering format. It defaults to: :html

You can also use the :plain rendering format to show the text without any formatting. The plain serializer is very basic and does not support any configuration, but it can be used as a starting point to create a new serializer.

PortableText accepts 2 methods, render and convert!.

  • render renders the content to the specified format defined in the to parameter. See How to render html ? for more information.
  • convert! converts the content to be used by the library.
    • It is useful for debugging purposes.
    • It transforms the keys to ruby format.
    • It creates the block types and mark definitions as objects, along with their children and marks, and creates a new data structure for list items.

How to render html?

Under the hood, the html renderer uses Phlex, a templating language which allows to create html in plain ruby.

content=[{"_key": "12345ffxx","_type": "block","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Hello world!"}],"markDefs": [],"style": "h1"}]portable_text=PortableText::Serializer.new(content: content,to: :html)# Since the HTML renderer uses Phlex, you can either include the rendering module # and use the render method...includePortableText::Html::Renderingrenderportable_text.render# => <h1>Hello world!</h1># ... Or you can directly call the Phlex templateportable_text.render.call# => <h1>Hello world!</h1>

Rails usage

To use the PortableText HTML serializer in rails, you need to add phlex-rails to the Gemfile.

You don’t need to do the whole phlex installation (as described in the Phlex documentation) if you don’t intend to use Phlex to replace your usual templating language.

gem'portable_text'gem'phlex-rails'

Then run bundle install

Then, in a controller or a view, just use render as usual.

portable_text=PortableText::Serializer.new(content: content,to: :html)renderportable_text.render

Configuration

This library is highly customizable through configuration. This is very straightforward as configuration is just a bunch of hashes that either define classes or key-value pairs.

Since this library is meant to be used for multiple use cases, and possibly several serializers at once, the type definitions are independent from the rendering.

So, in order to use a block type or a mark definition, one has to:

  • register it in the PortableText configuration, so it can be passed as an object to the serializer
  • create the template in the serializer (see HTML configuration)

Registering block types

content=[{"_key": "12345ffxx","_type": "myType", ...,"url": "https://www.github.com","image_url": "https://www.myimage.com/my_image.jpg","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Github"}]}]# Under the hood, this library uses dry-initializer.# You can use the option method to configure it easilyclassMyBlock < PortableText::BlockTypes::Baseoption:url,default: proc{""}option:image_url,default: proc{""}# children is an inherited option so it does not need to be added hereend# Or use plain old ruby. It needs to have attr_readers!classMyBlock < PortableText::BlockTypes::Baseattr_reader:url,:image_urldefinitialize(url: "",image_url:, **)super@url=url@image_url=image_urlendend# PortableText transforms keys to ruby format so use conventional ruby!# myType becomes my_type.PortableText.config.block.types.merge!{my_block: MyBlock}

Default block types

It’s probably a good idea to leave the list block type untouched. Change at your own risk.

{block: BlockTypes::Block,image: BlockTypes::Image,list: BlockTypes::List,span: BlockTypes::Span}

Registering mark definitions

It’s very similar to registering blocks. In case of doubt, refer to the block documentation.

content=[{"_key": "12345ffxx","_type": "block", ...,"markDefs": [{"_key"=>"456","_type"=>"newMarkDef"}],}]classNewMarkDef < PortableText::MarkDefs::Baseoption:label,default: proc{""}endPortableText.config.block.mark_defs.merge!{new_mark_def: NewMarkDef}

Html Serializer configuration

After registering your block type or mark definition, you need to create its template.

Each template takes one argument, a block.

Block Type Template

# Let's use the block defined earlier in Registering block typesclassHtml::MyBlock < PortableText::Html::BaseComponent# You can include PortableText::Html::Configured # to get access to the html serializer configuration helpers# The #config method allows you to access config values # The #block_type(:key) method is a shortcut to the relevant block_typeincludePortableText::Html::Configured# This library uses dry-initializer # so you can use `param` to create a simple parameter# There is no attribute_reader so `param :my_block` generates `@my_block`# This is recommended because some common HTML method names could conflict with# Phlex methods, like `title`. param:my_blockdefview_templatedivdoimg(src: @my_block.image_url)linkendendprivatedeflinka(href: @my_block.url)do@my_block.children.eachdo |child|
renderblock_type(:span).new(child,mark_defs: nil)endendendend# It needs to have the same key as the one registered before.PortableText::Html.config.block.types.merge!{my_block: Html::MyBlock}

Mark Definition template

Each mark definition takes one argument, a mark definition registered in the configuration.

# Let's use the mark definition defined earlier in Registering mark definitionclassHtml::NewMarkDef < PortableText::Html::BaseComponentparam:mark_def# &block is mandatory because mark definitions always contain other nodesdefview_template(&block)a(href: @mark_def.url){block.call}endend# It needs to have the same key as the one registered before.PortableText::Html.config.block.mark_defs.merge!{new_mark_def: Html::NewMarkDef}

Customizing html nodes

Every HTML node is customizable through config and looks this way:

h1: { node: :h1 }

You can add HTML attributes by appending them. For example:

h1: {node: :h1,class:"header"}

Configuring marks

You can configure marks by updating the marks setting.

PortableText::Html.config.span.marks.merge!{strong: {node: :b,}}# Defaults{strong: {node: :strong},em: {node: :em}}

Configuring styles

PortableText::Html.config.block.styles.merge!{h1: {node: :h3,class: "header"}}# Defaults{h1: {node: :h1},h2: {node: :h2},h3: {node: :h3},h4: {node: :h4},h5: {node: :h5},h6: {node: :h6},blockquote: {node: :blockquote},normal: {node: :p},li: {node: :li}}

Configuring list types

PortableText::Html.config.block.list_types.merge!{bullet: {node: :div}}# Defaults{bullet: {node: :ul},numeric: {node: :ol}}

Adding a new serializer

You can add a new serializer by creating a new class. You then need to add it the the config.

The serializer needs to have a content method and takes a list of blocks as only parameter.

classMySerializerdefinitialize(blocks)@blocks=blocksenddefcontent(**options)blocks.map |block|
block.type + " - " + block.key + " - " + options[:context]end.join(" ")endendPortableText.config.serializers.merge!{my_serializer: MySerializer}content=[{"_key": "12345ffxx","_type": "block", ... }]serializer=PortableText::Serializer.new(content: content,to: :my_serializer)# render forwards any keyword argument to the content method in the serializerserializer.render(context: "readme")# => block - 12345ffxx - readme

Acknowledgments

Thanks to Joel Drapper and Will Cosgrove for their help in building the HTML serializer!

About

A ruby renderer for Portable Text

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Ruby Portable Text

A ruby library to render Portable text

This gem is meant to be easy to use but is also highly configurable and extensible to match many use cases. By default, it can serialize Portable Text to HTML.

You can:

  • easily render default PortableText blocks in html without any configuration
  • create custom block types, mark_defs. Add them or replace existing ones.
  • create custom HTML serializers for each block type or mark def. Add them or replace existing ones.
  • customize each HTML node with custom attributes
  • create a new serializer

This is a very early release so please open issues if something doesn't work as intended.

Installation

gem install portable_text

Usage

See Rails usage for usage in rails

PortableText::Serializer takes 2 parameters:

  • content: , the portable text Array
  • to: , the rendering format. It defaults to: :html

You can also use the :plain rendering format to show the text without any formatting. The plain serializer is very basic and does not support any configuration, but it can be used as a starting point to create a new serializer.

PortableText accepts 2 methods, render and convert!.

  • render renders the content to the specified format defined in the to parameter. See How to render html ? for more information.
  • convert! converts the content to be used by the library.
    • It is useful for debugging purposes.
    • It transforms the keys to ruby format.
    • It creates the block types and mark definitions as objects, along with their children and marks, and creates a new data structure for list items.

How to render html?

Under the hood, the html renderer uses Phlex, a templating language which allows to create html in plain ruby.

content=[{"_key": "12345ffxx","_type": "block","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Hello world!"}],"markDefs": [],"style": "h1"}]portable_text=PortableText::Serializer.new(content: content,to: :html)# Since the HTML renderer uses Phlex, you can either include the rendering module # and use the render method...includePortableText::Html::Renderingrenderportable_text.render# => <h1>Hello world!</h1># ... Or you can directly call the Phlex templateportable_text.render.call# => <h1>Hello world!</h1>

Rails usage

To use the PortableText HTML serializer in rails, you need to add phlex-rails to the Gemfile.

You don’t need to do the whole phlex installation (as described in the Phlex documentation) if you don’t intend to use Phlex to replace your usual templating language.

gem'portable_text'gem'phlex-rails'

Then run bundle install

Then, in a controller or a view, just use render as usual.

portable_text=PortableText::Serializer.new(content: content,to: :html)renderportable_text.render

Configuration

This library is highly customizable through configuration. This is very straightforward as configuration is just a bunch of hashes that either define classes or key-value pairs.

Since this library is meant to be used for multiple use cases, and possibly several serializers at once, the type definitions are independent from the rendering.

So, in order to use a block type or a mark definition, one has to:

  • register it in the PortableText configuration, so it can be passed as an object to the serializer
  • create the template in the serializer (see HTML configuration)

Registering block types

content=[{"_key": "12345ffxx","_type": "myType", ...,"url": "https://www.github.com","image_url": "https://www.myimage.com/my_image.jpg","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Github"}]}]# Under the hood, this library uses dry-initializer.# You can use the option method to configure it easilyclassMyBlock < PortableText::BlockTypes::Baseoption:url,default: proc{""}option:image_url,default: proc{""}# children is an inherited option so it does not need to be added hereend# Or use plain old ruby. It needs to have attr_readers!classMyBlock < PortableText::BlockTypes::Baseattr_reader:url,:image_urldefinitialize(url: "",image_url:, **)super@url=url@image_url=image_urlendend# PortableText transforms keys to ruby format so use conventional ruby!# myType becomes my_type.PortableText.config.block.types.merge!{my_block: MyBlock}

Default block types

It’s probably a good idea to leave the list block type untouched. Change at your own risk.

{block: BlockTypes::Block,image: BlockTypes::Image,list: BlockTypes::List,span: BlockTypes::Span}

Registering mark definitions

It’s very similar to registering blocks. In case of doubt, refer to the block documentation.

content=[{"_key": "12345ffxx","_type": "block", ...,"markDefs": [{"_key"=>"456","_type"=>"newMarkDef"}],}]classNewMarkDef < PortableText::MarkDefs::Baseoption:label,default: proc{""}endPortableText.config.block.mark_defs.merge!{new_mark_def: NewMarkDef}

Html Serializer configuration

After registering your block type or mark definition, you need to create its template.

Each template takes one argument, a block.

Block Type Template

# Let's use the block defined earlier in Registering block typesclassHtml::MyBlock < PortableText::Html::BaseComponent# You can include PortableText::Html::Configured # to get access to the html serializer configuration helpers# The #config method allows you to access config values # The #block_type(:key) method is a shortcut to the relevant block_typeincludePortableText::Html::Configured# This library uses dry-initializer # so you can use `param` to create a simple parameter# There is no attribute_reader so `param :my_block` generates `@my_block`# This is recommended because some common HTML method names could conflict with# Phlex methods, like `title`. param:my_blockdefview_templatedivdoimg(src: @my_block.image_url)linkendendprivatedeflinka(href: @my_block.url)do@my_block.children.eachdo |child|
renderblock_type(:span).new(child,mark_defs: nil)endendendend# It needs to have the same key as the one registered before.PortableText::Html.config.block.types.merge!{my_block: Html::MyBlock}

Mark Definition template

Each mark definition takes one argument, a mark definition registered in the configuration.

# Let's use the mark definition defined earlier in Registering mark definitionclassHtml::NewMarkDef < PortableText::Html::BaseComponentparam:mark_def# &block is mandatory because mark definitions always contain other nodesdefview_template(&block)a(href: @mark_def.url){block.call}endend# It needs to have the same key as the one registered before.PortableText::Html.config.block.mark_defs.merge!{new_mark_def: Html::NewMarkDef}

Customizing html nodes

Every HTML node is customizable through config and looks this way:

h1: { node: :h1 }

You can add HTML attributes by appending them. For example:

h1: {node: :h1,class:"header"}

Configuring marks

You can configure marks by updating the marks setting.

PortableText::Html.config.span.marks.merge!{strong: {node: :b,}}# Defaults{strong: {node: :strong},em: {node: :em}}

Configuring styles

PortableText::Html.config.block.styles.merge!{h1: {node: :h3,class: "header"}}# Defaults{h1: {node: :h1},h2: {node: :h2},h3: {node: :h3},h4: {node: :h4},h5: {node: :h5},h6: {node: :h6},blockquote: {node: :blockquote},normal: {node: :p},li: {node: :li}}

Configuring list types

PortableText::Html.config.block.list_types.merge!{bullet: {node: :div}}# Defaults{bullet: {node: :ul},numeric: {node: :ol}}

Adding a new serializer

You can add a new serializer by creating a new class. You then need to add it the the config.

The serializer needs to have a content method and takes a list of blocks as only parameter.

classMySerializerdefinitialize(blocks)@blocks=blocksenddefcontent(**options)blocks.map |block|
block.type + " - " + block.key + " - " + options[:context]end.join(" ")endendPortableText.config.serializers.merge!{my_serializer: MySerializer}content=[{"_key": "12345ffxx","_type": "block", ... }]serializer=PortableText::Serializer.new(content: content,to: :my_serializer)# render forwards any keyword argument to the content method in the serializerserializer.render(context: "readme")# => block - 12345ffxx - readme

Acknowledgments

Thanks to Joel Drapper and Will Cosgrove for their help in building the HTML serializer!

About

A ruby renderer for Portable Text

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Ruby Portable Text

A ruby library to render Portable text

This gem is meant to be easy to use but is also highly configurable and extensible to match many use cases. By default, it can serialize Portable Text to HTML.

You can:

  • easily render default PortableText blocks in html without any configuration
  • create custom block types, mark_defs. Add them or replace existing ones.
  • create custom HTML serializers for each block type or mark def. Add them or replace existing ones.
  • customize each HTML node with custom attributes
  • create a new serializer

This is a very early release so please open issues if something doesn't work as intended.

Installation

gem install portable_text

Usage

See Rails usage for usage in rails

PortableText::Serializer takes 2 parameters:

  • content: , the portable text Array
  • to: , the rendering format. It defaults to: :html

You can also use the :plain rendering format to show the text without any formatting. The plain serializer is very basic and does not support any configuration, but it can be used as a starting point to create a new serializer.

PortableText accepts 2 methods, render and convert!.

  • render renders the content to the specified format defined in the to parameter. See How to render html ? for more information.
  • convert! converts the content to be used by the library.
    • It is useful for debugging purposes.
    • It transforms the keys to ruby format.
    • It creates the block types and mark definitions as objects, along with their children and marks, and creates a new data structure for list items.

How to render html?

Under the hood, the html renderer uses Phlex, a templating language which allows to create html in plain ruby.

content=[{"_key": "12345ffxx","_type": "block","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Hello world!"}],"markDefs": [],"style": "h1"}]portable_text=PortableText::Serializer.new(content: content,to: :html)# Since the HTML renderer uses Phlex, you can either include the rendering module # and use the render method...includePortableText::Html::Renderingrenderportable_text.render# => <h1>Hello world!</h1># ... Or you can directly call the Phlex templateportable_text.render.call# => <h1>Hello world!</h1>

Rails usage

To use the PortableText HTML serializer in rails, you need to add phlex-rails to the Gemfile.

You don’t need to do the whole phlex installation (as described in the Phlex documentation) if you don’t intend to use Phlex to replace your usual templating language.

gem'portable_text'gem'phlex-rails'

Then run bundle install

Then, in a controller or a view, just use render as usual.

portable_text=PortableText::Serializer.new(content: content,to: :html)renderportable_text.render

Configuration

This library is highly customizable through configuration. This is very straightforward as configuration is just a bunch of hashes that either define classes or key-value pairs.

Since this library is meant to be used for multiple use cases, and possibly several serializers at once, the type definitions are independent from the rendering.

So, in order to use a block type or a mark definition, one has to:

  • register it in the PortableText configuration, so it can be passed as an object to the serializer
  • create the template in the serializer (see HTML configuration)

Registering block types

content=[{"_key": "12345ffxx","_type": "myType", ...,"url": "https://www.github.com","image_url": "https://www.myimage.com/my_image.jpg","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Github"}]}]# Under the hood, this library uses dry-initializer.# You can use the option method to configure it easilyclassMyBlock < PortableText::BlockTypes::Baseoption:url,default: proc{""}option:image_url,default: proc{""}# children is an inherited option so it does not need to be added hereend# Or use plain old ruby. It needs to have attr_readers!classMyBlock < PortableText::BlockTypes::Baseattr_reader:url,:image_urldefinitialize(url: "",image_url:, **)super@url=url@image_url=image_urlendend# PortableText transforms keys to ruby format so use conventional ruby!# myType becomes my_type.PortableText.config.block.types.merge!{my_block: MyBlock}

Default block types

It’s probably a good idea to leave the list block type untouched. Change at your own risk.

{block: BlockTypes::Block,image: BlockTypes::Image,list: BlockTypes::List,span: BlockTypes::Span}

Registering mark definitions

It’s very similar to registering blocks. In case of doubt, refer to the block documentation.

content=[{"_key": "12345ffxx","_type": "block", ...,"markDefs": [{"_key"=>"456","_type"=>"newMarkDef"}],}]classNewMarkDef < PortableText::MarkDefs::Baseoption:label,default: proc{""}endPortableText.config.block.mark_defs.merge!{new_mark_def: NewMarkDef}

Html Serializer configuration

After registering your block type or mark definition, you need to create its template.

Each template takes one argument, a block.

Block Type Template

# Let's use the block defined earlier in Registering block typesclassHtml::MyBlock < PortableText::Html::BaseComponent# You can include PortableText::Html::Configured # to get access to the html serializer configuration helpers# The #config method allows you to access config values # The #block_type(:key) method is a shortcut to the relevant block_typeincludePortableText::Html::Configured# This library uses dry-initializer # so you can use `param` to create a simple parameter# There is no attribute_reader so `param :my_block` generates `@my_block`# This is recommended because some common HTML method names could conflict with# Phlex methods, like `title`. param:my_blockdefview_templatedivdoimg(src: @my_block.image_url)linkendendprivatedeflinka(href: @my_block.url)do@my_block.children.eachdo |child|
renderblock_type(:span).new(child,mark_defs: nil)endendendend# It needs to have the same key as the one registered before.PortableText::Html.config.block.types.merge!{my_block: Html::MyBlock}

Mark Definition template

Each mark definition takes one argument, a mark definition registered in the configuration.

# Let's use the mark definition defined earlier in Registering mark definitionclassHtml::NewMarkDef < PortableText::Html::BaseComponentparam:mark_def# &block is mandatory because mark definitions always contain other nodesdefview_template(&block)a(href: @mark_def.url){block.call}endend# It needs to have the same key as the one registered before.PortableText::Html.config.block.mark_defs.merge!{new_mark_def: Html::NewMarkDef}

Customizing html nodes

Every HTML node is customizable through config and looks this way:

h1: { node: :h1 }

You can add HTML attributes by appending them. For example:

h1: {node: :h1,class:"header"}

Configuring marks

You can configure marks by updating the marks setting.

PortableText::Html.config.span.marks.merge!{strong: {node: :b,}}# Defaults{strong: {node: :strong},em: {node: :em}}

Configuring styles

PortableText::Html.config.block.styles.merge!{h1: {node: :h3,class: "header"}}# Defaults{h1: {node: :h1},h2: {node: :h2},h3: {node: :h3},h4: {node: :h4},h5: {node: :h5},h6: {node: :h6},blockquote: {node: :blockquote},normal: {node: :p},li: {node: :li}}

Configuring list types

PortableText::Html.config.block.list_types.merge!{bullet: {node: :div}}# Defaults{bullet: {node: :ul},numeric: {node: :ol}}

Adding a new serializer

You can add a new serializer by creating a new class. You then need to add it the the config.

The serializer needs to have a content method and takes a list of blocks as only parameter.

classMySerializerdefinitialize(blocks)@blocks=blocksenddefcontent(**options)blocks.map |block|
block.type + " - " + block.key + " - " + options[:context]end.join(" ")endendPortableText.config.serializers.merge!{my_serializer: MySerializer}content=[{"_key": "12345ffxx","_type": "block", ... }]serializer=PortableText::Serializer.new(content: content,to: :my_serializer)# render forwards any keyword argument to the content method in the serializerserializer.render(context: "readme")# => block - 12345ffxx - readme

Acknowledgments

Thanks to Joel Drapper and Will Cosgrove for their help in building the HTML serializer!

About

A ruby renderer for Portable Text

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Ruby Portable Text

A ruby library to render Portable text

This gem is meant to be easy to use but is also highly configurable and extensible to match many use cases. By default, it can serialize Portable Text to HTML.

You can:

  • easily render default PortableText blocks in html without any configuration
  • create custom block types, mark_defs. Add them or replace existing ones.
  • create custom HTML serializers for each block type or mark def. Add them or replace existing ones.
  • customize each HTML node with custom attributes
  • create a new serializer

This is a very early release so please open issues if something doesn't work as intended.

Installation

gem install portable_text

Usage

See Rails usage for usage in rails

PortableText::Serializer takes 2 parameters:

  • content: , the portable text Array
  • to: , the rendering format. It defaults to: :html

You can also use the :plain rendering format to show the text without any formatting. The plain serializer is very basic and does not support any configuration, but it can be used as a starting point to create a new serializer.

PortableText accepts 2 methods, render and convert!.

  • render renders the content to the specified format defined in the to parameter. See How to render html ? for more information.
  • convert! converts the content to be used by the library.
    • It is useful for debugging purposes.
    • It transforms the keys to ruby format.
    • It creates the block types and mark definitions as objects, along with their children and marks, and creates a new data structure for list items.

How to render html?

Under the hood, the html renderer uses Phlex, a templating language which allows to create html in plain ruby.

content=[{"_key": "12345ffxx","_type": "block","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Hello world!"}],"markDefs": [],"style": "h1"}]portable_text=PortableText::Serializer.new(content: content,to: :html)# Since the HTML renderer uses Phlex, you can either include the rendering module # and use the render method...includePortableText::Html::Renderingrenderportable_text.render# => <h1>Hello world!</h1># ... Or you can directly call the Phlex templateportable_text.render.call# => <h1>Hello world!</h1>

Rails usage

To use the PortableText HTML serializer in rails, you need to add phlex-rails to the Gemfile.

You don’t need to do the whole phlex installation (as described in the Phlex documentation) if you don’t intend to use Phlex to replace your usual templating language.

gem'portable_text'gem'phlex-rails'

Then run bundle install

Then, in a controller or a view, just use render as usual.

portable_text=PortableText::Serializer.new(content: content,to: :html)renderportable_text.render

Configuration

This library is highly customizable through configuration. This is very straightforward as configuration is just a bunch of hashes that either define classes or key-value pairs.

Since this library is meant to be used for multiple use cases, and possibly several serializers at once, the type definitions are independent from the rendering.

So, in order to use a block type or a mark definition, one has to:

  • register it in the PortableText configuration, so it can be passed as an object to the serializer
  • create the template in the serializer (see HTML configuration)

Registering block types

content=[{"_key": "12345ffxx","_type": "myType", ...,"url": "https://www.github.com","image_url": "https://www.myimage.com/my_image.jpg","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Github"}]}]# Under the hood, this library uses dry-initializer.# You can use the option method to configure it easilyclassMyBlock < PortableText::BlockTypes::Baseoption:url,default: proc{""}option:image_url,default: proc{""}# children is an inherited option so it does not need to be added hereend# Or use plain old ruby. It needs to have attr_readers!classMyBlock < PortableText::BlockTypes::Baseattr_reader:url,:image_urldefinitialize(url: "",image_url:, **)super@url=url@image_url=image_urlendend# PortableText transforms keys to ruby format so use conventional ruby!# myType becomes my_type.PortableText.config.block.types.merge!{my_block: MyBlock}

Default block types

It’s probably a good idea to leave the list block type untouched. Change at your own risk.

{block: BlockTypes::Block,image: BlockTypes::Image,list: BlockTypes::List,span: BlockTypes::Span}

Registering mark definitions

It’s very similar to registering blocks. In case of doubt, refer to the block documentation.

content=[{"_key": "12345ffxx","_type": "block", ...,"markDefs": [{"_key"=>"456","_type"=>"newMarkDef"}],}]classNewMarkDef < PortableText::MarkDefs::Baseoption:label,default: proc{""}endPortableText.config.block.mark_defs.merge!{new_mark_def: NewMarkDef}

Html Serializer configuration

After registering your block type or mark definition, you need to create its template.

Each template takes one argument, a block.

Block Type Template

# Let's use the block defined earlier in Registering block typesclassHtml::MyBlock < PortableText::Html::BaseComponent# You can include PortableText::Html::Configured # to get access to the html serializer configuration helpers# The #config method allows you to access config values # The #block_type(:key) method is a shortcut to the relevant block_typeincludePortableText::Html::Configured# This library uses dry-initializer # so you can use `param` to create a simple parameter# There is no attribute_reader so `param :my_block` generates `@my_block`# This is recommended because some common HTML method names could conflict with# Phlex methods, like `title`. param:my_blockdefview_templatedivdoimg(src: @my_block.image_url)linkendendprivatedeflinka(href: @my_block.url)do@my_block.children.eachdo |child|
renderblock_type(:span).new(child,mark_defs: nil)endendendend# It needs to have the same key as the one registered before.PortableText::Html.config.block.types.merge!{my_block: Html::MyBlock}

Mark Definition template

Each mark definition takes one argument, a mark definition registered in the configuration.

# Let's use the mark definition defined earlier in Registering mark definitionclassHtml::NewMarkDef < PortableText::Html::BaseComponentparam:mark_def# &block is mandatory because mark definitions always contain other nodesdefview_template(&block)a(href: @mark_def.url){block.call}endend# It needs to have the same key as the one registered before.PortableText::Html.config.block.mark_defs.merge!{new_mark_def: Html::NewMarkDef}

Customizing html nodes

Every HTML node is customizable through config and looks this way:

h1: { node: :h1 }

You can add HTML attributes by appending them. For example:

h1: {node: :h1,class:"header"}

Configuring marks

You can configure marks by updating the marks setting.

PortableText::Html.config.span.marks.merge!{strong: {node: :b,}}# Defaults{strong: {node: :strong},em: {node: :em}}

Configuring styles

PortableText::Html.config.block.styles.merge!{h1: {node: :h3,class: "header"}}# Defaults{h1: {node: :h1},h2: {node: :h2},h3: {node: :h3},h4: {node: :h4},h5: {node: :h5},h6: {node: :h6},blockquote: {node: :blockquote},normal: {node: :p},li: {node: :li}}

Configuring list types

PortableText::Html.config.block.list_types.merge!{bullet: {node: :div}}# Defaults{bullet: {node: :ul},numeric: {node: :ol}}

Adding a new serializer

You can add a new serializer by creating a new class. You then need to add it the the config.

The serializer needs to have a content method and takes a list of blocks as only parameter.

classMySerializerdefinitialize(blocks)@blocks=blocksenddefcontent(**options)blocks.map |block|
block.type + " - " + block.key + " - " + options[:context]end.join(" ")endendPortableText.config.serializers.merge!{my_serializer: MySerializer}content=[{"_key": "12345ffxx","_type": "block", ... }]serializer=PortableText::Serializer.new(content: content,to: :my_serializer)# render forwards any keyword argument to the content method in the serializerserializer.render(context: "readme")# => block - 12345ffxx - readme

Acknowledgments

Thanks to Joel Drapper and Will Cosgrove for their help in building the HTML serializer!

About

A ruby renderer for Portable Text

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Ruby Portable Text

A ruby library to render Portable text

This gem is meant to be easy to use but is also highly configurable and extensible to match many use cases. By default, it can serialize Portable Text to HTML.

You can:

  • easily render default PortableText blocks in html without any configuration
  • create custom block types, mark_defs. Add them or replace existing ones.
  • create custom HTML serializers for each block type or mark def. Add them or replace existing ones.
  • customize each HTML node with custom attributes
  • create a new serializer

This is a very early release so please open issues if something doesn't work as intended.

Installation

gem install portable_text

Usage

See Rails usage for usage in rails

PortableText::Serializer takes 2 parameters:

  • content: , the portable text Array
  • to: , the rendering format. It defaults to: :html

You can also use the :plain rendering format to show the text without any formatting. The plain serializer is very basic and does not support any configuration, but it can be used as a starting point to create a new serializer.

PortableText accepts 2 methods, render and convert!.

  • render renders the content to the specified format defined in the to parameter. See How to render html ? for more information.
  • convert! converts the content to be used by the library.
    • It is useful for debugging purposes.
    • It transforms the keys to ruby format.
    • It creates the block types and mark definitions as objects, along with their children and marks, and creates a new data structure for list items.

How to render html?

Under the hood, the html renderer uses Phlex, a templating language which allows to create html in plain ruby.

content=[{"_key": "12345ffxx","_type": "block","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Hello world!"}],"markDefs": [],"style": "h1"}]portable_text=PortableText::Serializer.new(content: content,to: :html)# Since the HTML renderer uses Phlex, you can either include the rendering module # and use the render method...includePortableText::Html::Renderingrenderportable_text.render# => <h1>Hello world!</h1># ... Or you can directly call the Phlex templateportable_text.render.call# => <h1>Hello world!</h1>

Rails usage

To use the PortableText HTML serializer in rails, you need to add phlex-rails to the Gemfile.

You don’t need to do the whole phlex installation (as described in the Phlex documentation) if you don’t intend to use Phlex to replace your usual templating language.

gem'portable_text'gem'phlex-rails'

Then run bundle install

Then, in a controller or a view, just use render as usual.

portable_text=PortableText::Serializer.new(content: content,to: :html)renderportable_text.render

Configuration

This library is highly customizable through configuration. This is very straightforward as configuration is just a bunch of hashes that either define classes or key-value pairs.

Since this library is meant to be used for multiple use cases, and possibly several serializers at once, the type definitions are independent from the rendering.

So, in order to use a block type or a mark definition, one has to:

  • register it in the PortableText configuration, so it can be passed as an object to the serializer
  • create the template in the serializer (see HTML configuration)

Registering block types

content=[{"_key": "12345ffxx","_type": "myType", ...,"url": "https://www.github.com","image_url": "https://www.myimage.com/my_image.jpg","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Github"}]}]# Under the hood, this library uses dry-initializer.# You can use the option method to configure it easilyclassMyBlock < PortableText::BlockTypes::Baseoption:url,default: proc{""}option:image_url,default: proc{""}# children is an inherited option so it does not need to be added hereend# Or use plain old ruby. It needs to have attr_readers!classMyBlock < PortableText::BlockTypes::Baseattr_reader:url,:image_urldefinitialize(url: "",image_url:, **)super@url=url@image_url=image_urlendend# PortableText transforms keys to ruby format so use conventional ruby!# myType becomes my_type.PortableText.config.block.types.merge!{my_block: MyBlock}

Default block types

It’s probably a good idea to leave the list block type untouched. Change at your own risk.

{block: BlockTypes::Block,image: BlockTypes::Image,list: BlockTypes::List,span: BlockTypes::Span}

Registering mark definitions

It’s very similar to registering blocks. In case of doubt, refer to the block documentation.

content=[{"_key": "12345ffxx","_type": "block", ...,"markDefs": [{"_key"=>"456","_type"=>"newMarkDef"}],}]classNewMarkDef < PortableText::MarkDefs::Baseoption:label,default: proc{""}endPortableText.config.block.mark_defs.merge!{new_mark_def: NewMarkDef}

Html Serializer configuration

After registering your block type or mark definition, you need to create its template.

Each template takes one argument, a block.

Block Type Template

# Let's use the block defined earlier in Registering block typesclassHtml::MyBlock < PortableText::Html::BaseComponent# You can include PortableText::Html::Configured # to get access to the html serializer configuration helpers# The #config method allows you to access config values # The #block_type(:key) method is a shortcut to the relevant block_typeincludePortableText::Html::Configured# This library uses dry-initializer # so you can use `param` to create a simple parameter# There is no attribute_reader so `param :my_block` generates `@my_block`# This is recommended because some common HTML method names could conflict with# Phlex methods, like `title`. param:my_blockdefview_templatedivdoimg(src: @my_block.image_url)linkendendprivatedeflinka(href: @my_block.url)do@my_block.children.eachdo |child|
renderblock_type(:span).new(child,mark_defs: nil)endendendend# It needs to have the same key as the one registered before.PortableText::Html.config.block.types.merge!{my_block: Html::MyBlock}

Mark Definition template

Each mark definition takes one argument, a mark definition registered in the configuration.

# Let's use the mark definition defined earlier in Registering mark definitionclassHtml::NewMarkDef < PortableText::Html::BaseComponentparam:mark_def# &block is mandatory because mark definitions always contain other nodesdefview_template(&block)a(href: @mark_def.url){block.call}endend# It needs to have the same key as the one registered before.PortableText::Html.config.block.mark_defs.merge!{new_mark_def: Html::NewMarkDef}

Customizing html nodes

Every HTML node is customizable through config and looks this way:

h1: { node: :h1 }

You can add HTML attributes by appending them. For example:

h1: {node: :h1,class:"header"}

Configuring marks

You can configure marks by updating the marks setting.

PortableText::Html.config.span.marks.merge!{strong: {node: :b,}}# Defaults{strong: {node: :strong},em: {node: :em}}

Configuring styles

PortableText::Html.config.block.styles.merge!{h1: {node: :h3,class: "header"}}# Defaults{h1: {node: :h1},h2: {node: :h2},h3: {node: :h3},h4: {node: :h4},h5: {node: :h5},h6: {node: :h6},blockquote: {node: :blockquote},normal: {node: :p},li: {node: :li}}

Configuring list types

PortableText::Html.config.block.list_types.merge!{bullet: {node: :div}}# Defaults{bullet: {node: :ul},numeric: {node: :ol}}

Adding a new serializer

You can add a new serializer by creating a new class. You then need to add it the the config.

The serializer needs to have a content method and takes a list of blocks as only parameter.

classMySerializerdefinitialize(blocks)@blocks=blocksenddefcontent(**options)blocks.map |block|
block.type + " - " + block.key + " - " + options[:context]end.join(" ")endendPortableText.config.serializers.merge!{my_serializer: MySerializer}content=[{"_key": "12345ffxx","_type": "block", ... }]serializer=PortableText::Serializer.new(content: content,to: :my_serializer)# render forwards any keyword argument to the content method in the serializerserializer.render(context: "readme")# => block - 12345ffxx - readme

Acknowledgments

Thanks to Joel Drapper and Will Cosgrove for their help in building the HTML serializer!

About

A ruby renderer for Portable Text

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Ruby Portable Text

A ruby library to render Portable text

This gem is meant to be easy to use but is also highly configurable and extensible to match many use cases. By default, it can serialize Portable Text to HTML.

You can:

  • easily render default PortableText blocks in html without any configuration
  • create custom block types, mark_defs. Add them or replace existing ones.
  • create custom HTML serializers for each block type or mark def. Add them or replace existing ones.
  • customize each HTML node with custom attributes
  • create a new serializer

This is a very early release so please open issues if something doesn't work as intended.

Installation

gem install portable_text

Usage

See Rails usage for usage in rails

PortableText::Serializer takes 2 parameters:

  • content: , the portable text Array
  • to: , the rendering format. It defaults to: :html

You can also use the :plain rendering format to show the text without any formatting. The plain serializer is very basic and does not support any configuration, but it can be used as a starting point to create a new serializer.

PortableText accepts 2 methods, render and convert!.

  • render renders the content to the specified format defined in the to parameter. See How to render html ? for more information.
  • convert! converts the content to be used by the library.
    • It is useful for debugging purposes.
    • It transforms the keys to ruby format.
    • It creates the block types and mark definitions as objects, along with their children and marks, and creates a new data structure for list items.

How to render html?

Under the hood, the html renderer uses Phlex, a templating language which allows to create html in plain ruby.

content=[{"_key": "12345ffxx","_type": "block","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Hello world!"}],"markDefs": [],"style": "h1"}]portable_text=PortableText::Serializer.new(content: content,to: :html)# Since the HTML renderer uses Phlex, you can either include the rendering module # and use the render method...includePortableText::Html::Renderingrenderportable_text.render# => <h1>Hello world!</h1># ... Or you can directly call the Phlex templateportable_text.render.call# => <h1>Hello world!</h1>

Rails usage

To use the PortableText HTML serializer in rails, you need to add phlex-rails to the Gemfile.

You don’t need to do the whole phlex installation (as described in the Phlex documentation) if you don’t intend to use Phlex to replace your usual templating language.

gem'portable_text'gem'phlex-rails'

Then run bundle install

Then, in a controller or a view, just use render as usual.

portable_text=PortableText::Serializer.new(content: content,to: :html)renderportable_text.render

Configuration

This library is highly customizable through configuration. This is very straightforward as configuration is just a bunch of hashes that either define classes or key-value pairs.

Since this library is meant to be used for multiple use cases, and possibly several serializers at once, the type definitions are independent from the rendering.

So, in order to use a block type or a mark definition, one has to:

  • register it in the PortableText configuration, so it can be passed as an object to the serializer
  • create the template in the serializer (see HTML configuration)

Registering block types

content=[{"_key": "12345ffxx","_type": "myType", ...,"url": "https://www.github.com","image_url": "https://www.myimage.com/my_image.jpg","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Github"}]}]# Under the hood, this library uses dry-initializer.# You can use the option method to configure it easilyclassMyBlock < PortableText::BlockTypes::Baseoption:url,default: proc{""}option:image_url,default: proc{""}# children is an inherited option so it does not need to be added hereend# Or use plain old ruby. It needs to have attr_readers!classMyBlock < PortableText::BlockTypes::Baseattr_reader:url,:image_urldefinitialize(url: "",image_url:, **)super@url=url@image_url=image_urlendend# PortableText transforms keys to ruby format so use conventional ruby!# myType becomes my_type.PortableText.config.block.types.merge!{my_block: MyBlock}

Default block types

It’s probably a good idea to leave the list block type untouched. Change at your own risk.

{block: BlockTypes::Block,image: BlockTypes::Image,list: BlockTypes::List,span: BlockTypes::Span}

Registering mark definitions

It’s very similar to registering blocks. In case of doubt, refer to the block documentation.

content=[{"_key": "12345ffxx","_type": "block", ...,"markDefs": [{"_key"=>"456","_type"=>"newMarkDef"}],}]classNewMarkDef < PortableText::MarkDefs::Baseoption:label,default: proc{""}endPortableText.config.block.mark_defs.merge!{new_mark_def: NewMarkDef}

Html Serializer configuration

After registering your block type or mark definition, you need to create its template.

Each template takes one argument, a block.

Block Type Template

# Let's use the block defined earlier in Registering block typesclassHtml::MyBlock < PortableText::Html::BaseComponent# You can include PortableText::Html::Configured # to get access to the html serializer configuration helpers# The #config method allows you to access config values # The #block_type(:key) method is a shortcut to the relevant block_typeincludePortableText::Html::Configured# This library uses dry-initializer # so you can use `param` to create a simple parameter# There is no attribute_reader so `param :my_block` generates `@my_block`# This is recommended because some common HTML method names could conflict with# Phlex methods, like `title`. param:my_blockdefview_templatedivdoimg(src: @my_block.image_url)linkendendprivatedeflinka(href: @my_block.url)do@my_block.children.eachdo |child|
renderblock_type(:span).new(child,mark_defs: nil)endendendend# It needs to have the same key as the one registered before.PortableText::Html.config.block.types.merge!{my_block: Html::MyBlock}

Mark Definition template

Each mark definition takes one argument, a mark definition registered in the configuration.

# Let's use the mark definition defined earlier in Registering mark definitionclassHtml::NewMarkDef < PortableText::Html::BaseComponentparam:mark_def# &block is mandatory because mark definitions always contain other nodesdefview_template(&block)a(href: @mark_def.url){block.call}endend# It needs to have the same key as the one registered before.PortableText::Html.config.block.mark_defs.merge!{new_mark_def: Html::NewMarkDef}

Customizing html nodes

Every HTML node is customizable through config and looks this way:

h1: { node: :h1 }

You can add HTML attributes by appending them. For example:

h1: {node: :h1,class:"header"}

Configuring marks

You can configure marks by updating the marks setting.

PortableText::Html.config.span.marks.merge!{strong: {node: :b,}}# Defaults{strong: {node: :strong},em: {node: :em}}

Configuring styles

PortableText::Html.config.block.styles.merge!{h1: {node: :h3,class: "header"}}# Defaults{h1: {node: :h1},h2: {node: :h2},h3: {node: :h3},h4: {node: :h4},h5: {node: :h5},h6: {node: :h6},blockquote: {node: :blockquote},normal: {node: :p},li: {node: :li}}

Configuring list types

PortableText::Html.config.block.list_types.merge!{bullet: {node: :div}}# Defaults{bullet: {node: :ul},numeric: {node: :ol}}

Adding a new serializer

You can add a new serializer by creating a new class. You then need to add it the the config.

The serializer needs to have a content method and takes a list of blocks as only parameter.

classMySerializerdefinitialize(blocks)@blocks=blocksenddefcontent(**options)blocks.map |block|
block.type + " - " + block.key + " - " + options[:context]end.join(" ")endendPortableText.config.serializers.merge!{my_serializer: MySerializer}content=[{"_key": "12345ffxx","_type": "block", ... }]serializer=PortableText::Serializer.new(content: content,to: :my_serializer)# render forwards any keyword argument to the content method in the serializerserializer.render(context: "readme")# => block - 12345ffxx - readme

Acknowledgments

Thanks to Joel Drapper and Will Cosgrove for their help in building the HTML serializer!

About

A ruby renderer for Portable Text

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Ruby Portable Text

A ruby library to render Portable text

This gem is meant to be easy to use but is also highly configurable and extensible to match many use cases. By default, it can serialize Portable Text to HTML.

You can:

  • easily render default PortableText blocks in html without any configuration
  • create custom block types, mark_defs. Add them or replace existing ones.
  • create custom HTML serializers for each block type or mark def. Add them or replace existing ones.
  • customize each HTML node with custom attributes
  • create a new serializer

This is a very early release so please open issues if something doesn't work as intended.

Installation

gem install portable_text

Usage

See Rails usage for usage in rails

PortableText::Serializer takes 2 parameters:

  • content: , the portable text Array
  • to: , the rendering format. It defaults to: :html

You can also use the :plain rendering format to show the text without any formatting. The plain serializer is very basic and does not support any configuration, but it can be used as a starting point to create a new serializer.

PortableText accepts 2 methods, render and convert!.

  • render renders the content to the specified format defined in the to parameter. See How to render html ? for more information.
  • convert! converts the content to be used by the library.
    • It is useful for debugging purposes.
    • It transforms the keys to ruby format.
    • It creates the block types and mark definitions as objects, along with their children and marks, and creates a new data structure for list items.

How to render html?

Under the hood, the html renderer uses Phlex, a templating language which allows to create html in plain ruby.

content=[{"_key": "12345ffxx","_type": "block","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Hello world!"}],"markDefs": [],"style": "h1"}]portable_text=PortableText::Serializer.new(content: content,to: :html)# Since the HTML renderer uses Phlex, you can either include the rendering module # and use the render method...includePortableText::Html::Renderingrenderportable_text.render# => <h1>Hello world!</h1># ... Or you can directly call the Phlex templateportable_text.render.call# => <h1>Hello world!</h1>

Rails usage

To use the PortableText HTML serializer in rails, you need to add phlex-rails to the Gemfile.

You don’t need to do the whole phlex installation (as described in the Phlex documentation) if you don’t intend to use Phlex to replace your usual templating language.

gem'portable_text'gem'phlex-rails'

Then run bundle install

Then, in a controller or a view, just use render as usual.

portable_text=PortableText::Serializer.new(content: content,to: :html)renderportable_text.render

Configuration

This library is highly customizable through configuration. This is very straightforward as configuration is just a bunch of hashes that either define classes or key-value pairs.

Since this library is meant to be used for multiple use cases, and possibly several serializers at once, the type definitions are independent from the rendering.

So, in order to use a block type or a mark definition, one has to:

  • register it in the PortableText configuration, so it can be passed as an object to the serializer
  • create the template in the serializer (see HTML configuration)

Registering block types

content=[{"_key": "12345ffxx","_type": "myType", ...,"url": "https://www.github.com","image_url": "https://www.myimage.com/my_image.jpg","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Github"}]}]# Under the hood, this library uses dry-initializer.# You can use the option method to configure it easilyclassMyBlock < PortableText::BlockTypes::Baseoption:url,default: proc{""}option:image_url,default: proc{""}# children is an inherited option so it does not need to be added hereend# Or use plain old ruby. It needs to have attr_readers!classMyBlock < PortableText::BlockTypes::Baseattr_reader:url,:image_urldefinitialize(url: "",image_url:, **)super@url=url@image_url=image_urlendend# PortableText transforms keys to ruby format so use conventional ruby!# myType becomes my_type.PortableText.config.block.types.merge!{my_block: MyBlock}

Default block types

It’s probably a good idea to leave the list block type untouched. Change at your own risk.

{block: BlockTypes::Block,image: BlockTypes::Image,list: BlockTypes::List,span: BlockTypes::Span}

Registering mark definitions

It’s very similar to registering blocks. In case of doubt, refer to the block documentation.

content=[{"_key": "12345ffxx","_type": "block", ...,"markDefs": [{"_key"=>"456","_type"=>"newMarkDef"}],}]classNewMarkDef < PortableText::MarkDefs::Baseoption:label,default: proc{""}endPortableText.config.block.mark_defs.merge!{new_mark_def: NewMarkDef}

Html Serializer configuration

After registering your block type or mark definition, you need to create its template.

Each template takes one argument, a block.

Block Type Template

# Let's use the block defined earlier in Registering block typesclassHtml::MyBlock < PortableText::Html::BaseComponent# You can include PortableText::Html::Configured # to get access to the html serializer configuration helpers# The #config method allows you to access config values # The #block_type(:key) method is a shortcut to the relevant block_typeincludePortableText::Html::Configured# This library uses dry-initializer # so you can use `param` to create a simple parameter# There is no attribute_reader so `param :my_block` generates `@my_block`# This is recommended because some common HTML method names could conflict with# Phlex methods, like `title`. param:my_blockdefview_templatedivdoimg(src: @my_block.image_url)linkendendprivatedeflinka(href: @my_block.url)do@my_block.children.eachdo |child|
renderblock_type(:span).new(child,mark_defs: nil)endendendend# It needs to have the same key as the one registered before.PortableText::Html.config.block.types.merge!{my_block: Html::MyBlock}

Mark Definition template

Each mark definition takes one argument, a mark definition registered in the configuration.

# Let's use the mark definition defined earlier in Registering mark definitionclassHtml::NewMarkDef < PortableText::Html::BaseComponentparam:mark_def# &block is mandatory because mark definitions always contain other nodesdefview_template(&block)a(href: @mark_def.url){block.call}endend# It needs to have the same key as the one registered before.PortableText::Html.config.block.mark_defs.merge!{new_mark_def: Html::NewMarkDef}

Customizing html nodes

Every HTML node is customizable through config and looks this way:

h1: { node: :h1 }

You can add HTML attributes by appending them. For example:

h1: {node: :h1,class:"header"}

Configuring marks

You can configure marks by updating the marks setting.

PortableText::Html.config.span.marks.merge!{strong: {node: :b,}}# Defaults{strong: {node: :strong},em: {node: :em}}

Configuring styles

PortableText::Html.config.block.styles.merge!{h1: {node: :h3,class: "header"}}# Defaults{h1: {node: :h1},h2: {node: :h2},h3: {node: :h3},h4: {node: :h4},h5: {node: :h5},h6: {node: :h6},blockquote: {node: :blockquote},normal: {node: :p},li: {node: :li}}

Configuring list types

PortableText::Html.config.block.list_types.merge!{bullet: {node: :div}}# Defaults{bullet: {node: :ul},numeric: {node: :ol}}

Adding a new serializer

You can add a new serializer by creating a new class. You then need to add it the the config.

The serializer needs to have a content method and takes a list of blocks as only parameter.

classMySerializerdefinitialize(blocks)@blocks=blocksenddefcontent(**options)blocks.map |block|
block.type + " - " + block.key + " - " + options[:context]end.join(" ")endendPortableText.config.serializers.merge!{my_serializer: MySerializer}content=[{"_key": "12345ffxx","_type": "block", ... }]serializer=PortableText::Serializer.new(content: content,to: :my_serializer)# render forwards any keyword argument to the content method in the serializerserializer.render(context: "readme")# => block - 12345ffxx - readme

Acknowledgments

Thanks to Joel Drapper and Will Cosgrove for their help in building the HTML serializer!

About

A ruby renderer for Portable Text

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Ruby Portable Text

A ruby library to render Portable text

This gem is meant to be easy to use but is also highly configurable and extensible to match many use cases. By default, it can serialize Portable Text to HTML.

You can:

  • easily render default PortableText blocks in html without any configuration
  • create custom block types, mark_defs. Add them or replace existing ones.
  • create custom HTML serializers for each block type or mark def. Add them or replace existing ones.
  • customize each HTML node with custom attributes
  • create a new serializer

This is a very early release so please open issues if something doesn't work as intended.

Installation

gem install portable_text

Usage

See Rails usage for usage in rails

PortableText::Serializer takes 2 parameters:

  • content: , the portable text Array
  • to: , the rendering format. It defaults to: :html

You can also use the :plain rendering format to show the text without any formatting. The plain serializer is very basic and does not support any configuration, but it can be used as a starting point to create a new serializer.

PortableText accepts 2 methods, render and convert!.

  • render renders the content to the specified format defined in the to parameter. See How to render html ? for more information.
  • convert! converts the content to be used by the library.
    • It is useful for debugging purposes.
    • It transforms the keys to ruby format.
    • It creates the block types and mark definitions as objects, along with their children and marks, and creates a new data structure for list items.

How to render html?

Under the hood, the html renderer uses Phlex, a templating language which allows to create html in plain ruby.

content=[{"_key": "12345ffxx","_type": "block","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Hello world!"}],"markDefs": [],"style": "h1"}]portable_text=PortableText::Serializer.new(content: content,to: :html)# Since the HTML renderer uses Phlex, you can either include the rendering module # and use the render method...includePortableText::Html::Renderingrenderportable_text.render# => <h1>Hello world!</h1># ... Or you can directly call the Phlex templateportable_text.render.call# => <h1>Hello world!</h1>

Rails usage

To use the PortableText HTML serializer in rails, you need to add phlex-rails to the Gemfile.

You don’t need to do the whole phlex installation (as described in the Phlex documentation) if you don’t intend to use Phlex to replace your usual templating language.

gem'portable_text'gem'phlex-rails'

Then run bundle install

Then, in a controller or a view, just use render as usual.

portable_text=PortableText::Serializer.new(content: content,to: :html)renderportable_text.render

Configuration

This library is highly customizable through configuration. This is very straightforward as configuration is just a bunch of hashes that either define classes or key-value pairs.

Since this library is meant to be used for multiple use cases, and possibly several serializers at once, the type definitions are independent from the rendering.

So, in order to use a block type or a mark definition, one has to:

  • register it in the PortableText configuration, so it can be passed as an object to the serializer
  • create the template in the serializer (see HTML configuration)

Registering block types

content=[{"_key": "12345ffxx","_type": "myType", ...,"url": "https://www.github.com","image_url": "https://www.myimage.com/my_image.jpg","children": [{"_key": "78910xxyy","_type": "span","marks": [],"text": "Github"}]}]# Under the hood, this library uses dry-initializer.# You can use the option method to configure it easilyclassMyBlock < PortableText::BlockTypes::Baseoption:url,default: proc{""}option:image_url,default: proc{""}# children is an inherited option so it does not need to be added hereend# Or use plain old ruby. It needs to have attr_readers!classMyBlock < PortableText::BlockTypes::Baseattr_reader:url,:image_urldefinitialize(url: "",image_url:, **)super@url=url@image_url=image_urlendend# PortableText transforms keys to ruby format so use conventional ruby!# myType becomes my_type.PortableText.config.block.types.merge!{my_block: MyBlock}

Default block types

It’s probably a good idea to leave the list block type untouched. Change at your own risk.

{block: BlockTypes::Block,image: BlockTypes::Image,list: BlockTypes::List,span: BlockTypes::Span}

Registering mark definitions

It’s very similar to registering blocks. In case of doubt, refer to the block documentation.

content=[{"_key": "12345ffxx","_type": "block", ...,"markDefs": [{"_key"=>"456","_type"=>"newMarkDef"}],}]classNewMarkDef < PortableText::MarkDefs::Baseoption:label,default: proc{""}endPortableText.config.block.mark_defs.merge!{new_mark_def: NewMarkDef}

Html Serializer configuration

After registering your block type or mark definition, you need to create its template.

Each template takes one argument, a block.

Block Type Template

# Let's use the block defined earlier in Registering block typesclassHtml::MyBlock < PortableText::Html::BaseComponent# You can include PortableText::Html::Configured # to get access to the html serializer configuration helpers# The #config method allows you to access config values # The #block_type(:key) method is a shortcut to the relevant block_typeincludePortableText::Html::Configured# This library uses dry-initializer # so you can use `param` to create a simple parameter# There is no attribute_reader so `param :my_block` generates `@my_block`# This is recommended because some common HTML method names could conflict with# Phlex methods, like `title`. param:my_blockdefview_templatedivdoimg(src: @my_block.image_url)linkendendprivatedeflinka(href: @my_block.url)do@my_block.children.eachdo |child|
renderblock_type(:span).new(child,mark_defs: nil)endendendend# It needs to have the same key as the one registered before.PortableText::Html.config.block.types.merge!{my_block: Html::MyBlock}

Mark Definition template

Each mark definition takes one argument, a mark definition registered in the configuration.

# Let's use the mark definition defined earlier in Registering mark definitionclassHtml::NewMarkDef < PortableText::Html::BaseComponentparam:mark_def# &block is mandatory because mark definitions always contain other nodesdefview_template(&block)a(href: @mark_def.url){block.call}endend# It needs to have the same key as the one registered before.PortableText::Html.config.block.mark_defs.merge!{new_mark_def: Html::NewMarkDef}

Customizing html nodes

Every HTML node is customizable through config and looks this way:

h1: { node: :h1 }

You can add HTML attributes by appending them. For example:

h1: {node: :h1,class:"header"}

Configuring marks

You can configure marks by updating the marks setting.

PortableText::Html.config.span.marks.merge!{strong: {node: :b,}}# Defaults{strong: {node: :strong},em: {node: :em}}

Configuring styles

PortableText::Html.config.block.styles.merge!{h1: {node: :h3,class: "header"}}# Defaults{h1: {node: :h1},h2: {node: :h2},h3: {node: :h3},h4: {node: :h4},h5: {node: :h5},h6: {node: :h6},blockquote: {node: :blockquote},normal: {node: :p},li: {node: :li}}

Configuring list types

PortableText::Html.config.block.list_types.merge!{bullet: {node: :div}}# Defaults{bullet: {node: :ul},numeric: {node: :ol}}

Adding a new serializer

You can add a new serializer by creating a new class. You then need to add it the the config.

The serializer needs to have a content method and takes a list of blocks as only parameter.

classMySerializerdefinitialize(blocks)@blocks=blocksenddefcontent(**options)blocks.map |block|
block.type + " - " + block.key + " - " + options[:context]end.join(" ")endendPortableText.config.serializers.merge!{my_serializer: MySerializer}content=[{"_key": "12345ffxx","_type": "block", ... }]serializer=PortableText::Serializer.new(content: content,to: :my_serializer)# render forwards any keyword argument to the content method in the serializerserializer.render(context: "readme")# => block - 12345ffxx - readme

Acknowledgments

Thanks to Joel Drapper and Will Cosgrove for their help in building the HTML serializer!

About

A ruby renderer for Portable Text

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages