# Created 2026-03-18 mer. 01:43
#+title: 7d.nz
#+author: Phil. Estival
* • [2017-05-26 ven.] Org mode to JSX                             :react:org:
How to quickly turn an org-mode tree to JSX ?

org-mode is made of lisp and lisp can easily be turned
to json.  The following does so, removing cycles, even
if we could keep them with objects{} and references.

The other one below turns that JSON to a react JSX
tree. So technically, it's not turning seamlessly
org-mode to JSX and the JSON file has to be provided
to the JS interperter of the browser.

Converting an elisp org structure to JSON
brings a lot of insight about org itself.

: org-to-json

#+begin_src emacs-lisp
  ;; Answer given by John Kitchin on stackx
  (require 'json)
  (defun org-export-json-buffer ()
    (interactive)
    (let* ((tree (org-element-parse-buffer 'object nil)))
      (org-element-map tree (append org-element-all-elements
                                    org-element-all-objects '(plain-text))
        (lambda (x)
          (if (org-element-property :parent x)
              (org-element-put-property x :parent "none"))
          (if (org-element-property :structure x)
              (org-element-put-property x :structure "none")))
        )
      (with-current-buffer (get-buffer-create "*ox-json*")
        (erase-buffer)
        (insert (json-encode tree))
        (json-pretty-print-buffer))
      (switch-to-buffer (get-buffer-create "*ox-json*"))))

  (defun org-to-json (query)
    "Export headings that match QUERY to json."
    (org-map-entries
     (lambda ()
       (org-copy-subtree)
       (with-current-buffer (get-buffer-create "-t-")
         (yank)))
     query)
    (with-current-buffer "-t-" (org-export-json-buffer))
    (kill-buffer "-t-"))

  (defun org-export-published-to-json ()
    (org-map-entries
     (lambda ()
       (org-copy-subtree)
       (with-current-buffer (get-buffer-create "-t-")
         (yank)))
     "TODO=\"PUBLISHED\"")
    (with-current-buffer "-t-" (org-export-json-buffer))
    (kill-buffer "-t-"))
#+end_src

: json2vdom

#+begin_src js
  /**
   ,* org→json→preact
   ,* author: 𝙋𝙝𝙞𝙡 𝙀𝙨𝙩𝙞𝙫𝙖𝙡 @ 𝙛𝙧𝙚𝙚ㆍ𝙛𝙧  (c) 2016
   ,* json2vdom.js
   ,* date:<26/05/17>
   ,*/

  import {h} from 'preact';
  import {CLS} from './orgnodes'


  /* name, properties, children...(=> a vnode)
     [ name, [ {propA:x, propsB:y,...} ], [ children...] ] →
     <node propA='x' propB='y'>  <children...> </node>
  ,*/
  export const rendervnode =(name, attributes, ...children)=>
    h(
      CLS[name] || 'div',
      {
        ...attributes,
        ...{class: name},
      },
      (children.length && children[0]) ? renderList(children) : null
    )


  export const renderList =(a)=>  a?.map(e => {
    if (typeof e === 'string') {
      return e
    }
    if (Array.isArray(e)) {
      return rendervnode(...e)
    }
  }
#+end_src
here is an alternative to write directly the vdom to the window.document
https://jasonformat.com/wtf-is-jsx/

: preact-org.json

#+begin_src js
  /**
   ,* preact-org-json
   ,* @author Phil ESTIVAL
   ,* Date:<05/04/17$ 20:45$>
   ,* license : GPL
   ,* org-json-nodes.js
   ,*
   ,* first letters of the orgjson's element are not capital
   ,* that's why here some classes names aren't too.
   ,* Those can't be instanciated from inside code (react convention)
   ,* but can be associated in the export below
   ,* or subclassed if needed
   ,*
   ,* todo : fix the [null] element in empty links
   ,*/

  import {Component} from 'preact'
  import {renderList} from 'json2vdom.js'

  class keyword extends Component {
      render =()=>
          <div class="keyword">
          <div class={this.props.kw}>{this.props.kw}</div>
          <div class={this.props.kw}>{this.props.value}</div>
          </div>;
  }


  class Priority extends Component {
      render =()=> <div class="priority">{ String.fromCharCode(this.props.priority) } </div>
  }


  class headline extends Component {
      // a ctor for the headline will prevent upper nodes from rendering

      toggle() {
          this.setState({open:!this.state.open})
      }

      constructor() {
          super(...arguments);
          this.setState({open:true})
      }

      render({children},{},{}) { // yup, this.props.children

          this.id = this.props.title && this.props.title.length>1 && this.props.title[1] || this.props.title; // ??? bof
          //console.log('>>>>' + this.id, this.props.title)
          this.title = renderList(this.props.title);
          //this.tags = this.props.tags && this.props.tags.join(", ")
          this.priority = this.props.priority && <Priority priority={this.props.priority}/>;
          return (
                  <div class="headline">
                  <h2 class={"L"+this.props.level}
              id={this.id}

                  >{/*onClick={()=>{this.toggle();}}*/}
              {this.priority} {this.title} {!this.state.open && '...' }
              </h2>
                  {/*<div class="tags"> {this.id}
                     { this.tags }
                     </div>*/}
              {this.state.open &&  children }
              </div>
          );
      }
  }

  const Timestamp =(props)=>
        <div class={props.class}>
          { props['year-start']}-{props['month-start']}-{props['day-start']}
        </div>



  const Planning =(props)=>
     <div {...this.props}>
       { props.closed    ? <div> closed    <Timestamp {...props.closed[1]   }/></div> : '' }
       { props.scheduled ? <div> scheduled <Timestamp {...props.scheduled[1]}/></div> : '' }
       { props.deadline  ? <div> deadline  <Timestamp {...props.deadline[1] }/></div> : '' }
     </div>


  const Item =(props)=> <li {...props}>{props.children}</li>

  const imageExtensionsRgx = new RegExp("(" + [
      "png", "jpeg", "jpg", "gif", "svg", "bmp", "tiff",
      "tif", "xbm", "xpm", "pbm", "pgm", "ppm", "svg"
  ].join("|") + ")$", "i");

  const ImgExt = [
      "png", "jpeg", "jpg", "gif", "svg", "bmp", "tiff",
      "tif", "xbm", "xpm", "pbm", "pgm", "ppm", "svg",
      "webm",
      "mp3","ogg"
  ];
  const MovieExt = ["webm"];
  const AudioExt = [ "mp3", "ogg"];
  const fileExt = ImgExt.concat(MovieExt.concat(AudioExt));
  const FileExtRgx = new RegExp("(" + fileExt.join("|") + ")$", "i");


  class link extends Component {

      constructor() {
          super(...arguments);
          let type, link;
          //if (!this.props['rawlink']) this.props['rawlink'] = this.link
          // console.log(this.props['raw-link'])
          this.rawlink = this.props['raw-link'];
          this.link = this.linkMapping(this.props['type']);

          if (this.link.startsWith('#')) {
              this.anchortag = ()=>{
                  alert(this.link);
                  document.getElementById(this.link.slice(1)).scrollIntoView()
              }
          }
          let ext = FileExtRgx.exec(this.link);

          // handle image link
          let ch = this.props.children;

          if ( ext ) {
              if ( ImgExt.includes(ext[0]) ) {
                  this.tag = <img src={this.link}/>
              }
              else if ( MovieExt.includes(ext[0]) ) {
                  this.tag = <Movie src={this.link}/>
              }
              else if ( AudioExt.includes(ext[0]) ) {
                  this.tag = <Audio src={this.link}/>
              }
          }
          else {
              let ch = this.props.children;
              let innerlink = null;
              if (ch && ch.length && ch[0] !== null) {
                  console.log(ch, typeof(ch[0]));
                  if((ch.length === 1) && ( typeof ch[0] === "string")) {
                      // check for an image link
                      let ext = FileExtRgx.exec(ch[0]);
                      if ( ext ) {
                          if ( ImgExt.includes(ext[0]) ) {
                              innerlink = <img src={ch[0]}/>
                          }
                      }
                  }
                  else
                      innerlink = renderList(ch);
              }
              else innerlink = this.rawlink;
              this.tag = <a href={this.link}>{ innerlink} </a>
          }
      }

      linkMapping(type) {
          let rawlink = this.rawlink;
          switch (this.props['type']) {
          case 'http':
          case 'https':
          case 'file':
              return rawlink;
          case 'custom-id':
              return rawlink;
          case 'fuzzy':
              return '/section/' + rawlink.replace(' ','%20');
          default:
              console.log('unknown type');
              return rawlink

          }
      }

      render =()=> this.tag

  }

  class target {
      render =()=> <div id={this.props.value}/>

  }

  class Div extends Component{

      postblanks() {
          {Array(this.props['post-blank']).fill().map((_,i) => <br/>)}
      }
  }

  class paragraph extends Div {

      constructor() {
          super(...arguments);
          this.class = "paragraph "+ (this.props.attr_align?this.props.attr_align:'')
          let name = this.props['name'];
          if(name) {
              this.id = name.startsWith('#') ? name.slice(1) : name
          }
      }

      render() {
          let name = this.props['name'];
          //let time = new Date().toLocaleTimeString();
          return <div class={this.class}
          id={this.id = name && (name.startsWith('#') ? name.slice(1) : name)}>
              {this.props.children}
          {super.postblanks()}
          </div>;
      }
  }

  class SrcBlock extends Component {

      static lang = {
          python : 'py'
      }

      render =()=>
          <div class="src-block">
          <pre class="prettyprint">{this.props.value}</pre>
          </div>
  }


  class table extends Div {

      render =()=>
          <table>
          {this.props.children}
      {super.postblanks()}
      </table>
  }


  class TableRow extends Div {

      render =()=>
          <tr {...this.props}>
          {this.props.children}
      {super.postblanks()}
      </tr>
  }


  class TableCell extends Div {

      render =()=>
          <td>
          {this.props.children}
      {super.postblanks()}
      </td>
  }


  // or as function cls(x) + optional default
  import styles from './styles';
  export const CLS = {
      ...styles,
      ...{headline, keyword, timestamp, link, target,item, paragraph, planning,
          'src-block':SrcBlock, table, 'table-row':TableRow, 'table-cell':TableCell
         }};
#+end_src