github.com/rpdict/ponzu@v0.10.1-0.20190226054626-477f29d6bf5e/management/editor/editor.go (about)

     1  // Package editor enables users to create edit views from their content
     2  // structs so that admins can manage content
     3  package editor
     4  
     5  import (
     6  	"bytes"
     7  	"log"
     8  	"net/http"
     9  )
    10  
    11  // Editable ensures data is editable
    12  type Editable interface {
    13  	MarshalEditor() ([]byte, error)
    14  }
    15  
    16  // Mergeable allows external post content to be approved and published through
    17  // the public-facing API
    18  type Mergeable interface {
    19  	// Approve copies an external post to the internal collection and triggers
    20  	// a re-sort of its content type posts
    21  	Approve(http.ResponseWriter, *http.Request) error
    22  }
    23  
    24  // Editor is a view containing fields to manage content
    25  type Editor struct {
    26  	ViewBuf *bytes.Buffer
    27  }
    28  
    29  // Field is used to create the editable view for a field
    30  // within a particular content struct
    31  type Field struct {
    32  	View []byte
    33  }
    34  
    35  // Form takes editable content and any number of Field funcs to describe the edit
    36  // page for any content struct added by a user
    37  func Form(post Editable, fields ...Field) ([]byte, error) {
    38  	editor := &Editor{}
    39  
    40  	editor.ViewBuf = &bytes.Buffer{}
    41  	_, err := editor.ViewBuf.WriteString(`<table><tbody class="row"><tr class="col s8 editor-fields"><td class="col s12">`)
    42  	if err != nil {
    43  		log.Println("Error writing HTML string to editor Form buffer")
    44  		return nil, err
    45  	}
    46  
    47  	for _, f := range fields {
    48  		addFieldToEditorView(editor, f)
    49  	}
    50  
    51  	_, err = editor.ViewBuf.WriteString(`</td></tr>`)
    52  	if err != nil {
    53  		log.Println("Error writing HTML string to editor Form buffer")
    54  		return nil, err
    55  	}
    56  
    57  	// content items with Item embedded have some default fields we need to render
    58  	_, err = editor.ViewBuf.WriteString(`<tr class="col s4 default-fields"><td class="col s12">`)
    59  	if err != nil {
    60  		log.Println("Error writing HTML string to editor Form buffer")
    61  		return nil, err
    62  	}
    63  
    64  	publishTime := `
    65  <div class="row content-only __ponzu">
    66  	<div class="input-field col s6">
    67  		<label class="active">MM</label>
    68  		<select class="month __ponzu browser-default">
    69  			<option value="1">Jan - 01</option>
    70  			<option value="2">Feb - 02</option>
    71  			<option value="3">Mar - 03</option>
    72  			<option value="4">Apr - 04</option>
    73  			<option value="5">May - 05</option>
    74  			<option value="6">Jun - 06</option>
    75  			<option value="7">Jul - 07</option>
    76  			<option value="8">Aug - 08</option>
    77  			<option value="9">Sep - 09</option>
    78  			<option value="10">Oct - 10</option>
    79  			<option value="11">Nov - 11</option>
    80  			<option value="12">Dec - 12</option>
    81  		</select>
    82  	</div>
    83  	<div class="input-field col s2">
    84  		<label class="active">DD</label>
    85  		<input value="" class="day __ponzu" maxlength="2" type="text" placeholder="DD" />
    86  	</div>
    87  	<div class="input-field col s4">
    88  		<label class="active">YYYY</label>
    89  		<input value="" class="year __ponzu" maxlength="4" type="text" placeholder="YYYY" />
    90  	</div>
    91  </div>
    92  
    93  <div class="row content-only __ponzu">
    94  	<div class="input-field col s3">
    95  		<label class="active">HH</label>
    96  		<input value="" class="hour __ponzu" maxlength="2" type="text" placeholder="HH" />
    97  	</div>
    98  	<div class="col s1">:</div>
    99  	<div class="input-field col s3">
   100  		<label class="active">MM</label>
   101  		<input value="" class="minute __ponzu" maxlength="2" type="text" placeholder="MM" />
   102  	</div>
   103  	<div class="input-field col s4">
   104  		<label class="active">Period</label>
   105  		<select class="period __ponzu browser-default">
   106  			<option value="AM">AM</option>
   107  			<option value="PM">PM</option>
   108  		</select>
   109  	</div>
   110  </div>
   111  	`
   112  
   113  	_, err = editor.ViewBuf.WriteString(publishTime)
   114  	if err != nil {
   115  		log.Println("Error writing HTML string to editor Form buffer")
   116  		return nil, err
   117  	}
   118  
   119  	err = addPostDefaultFieldsToEditorView(post, editor)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	submit := `
   125  <div class="input-field post-controls">
   126  	<button class="right waves-effect waves-light btn green save-post" type="submit">Save</button>
   127  	<button class="right waves-effect waves-light btn red delete-post" type="submit">Delete</button>
   128  </div>
   129  `
   130  	_, ok := post.(Mergeable)
   131  	if ok {
   132  		submit +=
   133  			`
   134  <div class="row external post-controls">
   135  	<div class="col s12 input-field">
   136  		<button class="right waves-effect waves-light btn blue approve-post" type="submit">Approve</button>
   137  		<button class="right waves-effect waves-light btn grey darken-2 reject-post" type="submit">Reject</button>
   138  	</div>	
   139  	<label class="approve-details right-align col s12">This content is pending approval. By clicking 'Approve', it will be immediately published. By clicking 'Reject', it will be deleted.</label> 
   140  </div>
   141  `
   142  	}
   143  
   144  	script := `
   145  <script>
   146  	$(function() {
   147  		var form = $('form'),
   148  			save = form.find('button.save-post'),
   149  			del = form.find('button.delete-post'),
   150  			external = form.find('.post-controls.external'),
   151  			id = form.find('input[name=id]'),
   152  			timestamp = $('.__ponzu.content-only'),
   153  			slug = $('input[name=slug]');
   154  		
   155  		// hide if this is a new post, or a non-post editor page
   156  		if (id.val() === '-1' || form.attr('action') !== '/admin/edit') {
   157  			del.hide();
   158  			external.hide();
   159  		}
   160  
   161  		// hide approval if not on a pending content item
   162  		if (getParam('status') !== 'pending') {
   163  			external.hide();
   164  		} 
   165  
   166  		// no timestamp, slug visible on addons
   167  		if (form.attr('action') === '/admin/addon') {
   168  			timestamp.hide();
   169  			slug.parent().hide();
   170  		}
   171  
   172  		save.on('click', function(e) {
   173  			e.preventDefault();
   174  
   175  			if (getParam('status') === 'pending') {
   176  				var action = form.attr('action');
   177  				form.attr('action', action + '?status=pending')
   178  			}
   179  
   180  			form.submit();
   181  		});
   182  
   183  		del.on('click', function(e) {
   184  			e.preventDefault();
   185  			var action = form.attr('action');
   186  			action = action + '/delete';
   187  			form.attr('action', action);
   188  			
   189  			if (confirm("[Ponzu] Please confirm:\n\nAre you sure you want to delete this post?\nThis cannot be undone.")) {
   190  				form.submit();
   191  			}
   192  		});
   193  
   194  		external.find('button.approve-post').on('click', function(e) {
   195  			e.preventDefault();
   196  			var action = form.attr('action');
   197  			action = action + '/approve';
   198  			form.attr('action', action);
   199  
   200  			form.submit();
   201  		});
   202  
   203  		external.find('button.reject-post').on('click', function(e) {
   204  			e.preventDefault();
   205  			var action = form.attr('action');
   206  			action = action + '/delete?reject=true';
   207  			form.attr('action', action);
   208  
   209  			if (confirm("[Ponzu] Please confirm:\n\nAre you sure you want to reject this post?\nDoing so will delete it, and cannot be undone.")) {
   210  				form.submit();
   211  			}
   212  		});
   213  	});
   214  </script>
   215  `
   216  	_, err = editor.ViewBuf.WriteString(submit + script + `</td></tr></tbody></table>`)
   217  	if err != nil {
   218  		log.Println("Error writing HTML string to editor Form buffer")
   219  		return nil, err
   220  	}
   221  
   222  	return editor.ViewBuf.Bytes(), nil
   223  }
   224  
   225  func addFieldToEditorView(e *Editor, f Field) error {
   226  	_, err := e.ViewBuf.Write(f.View)
   227  	if err != nil {
   228  		log.Println("Error writing field view to editor view buffer")
   229  		return err
   230  	}
   231  
   232  	return nil
   233  }
   234  
   235  func addPostDefaultFieldsToEditorView(p Editable, e *Editor) error {
   236  	defaults := []Field{
   237  		{
   238  			View: Input("Slug", p, map[string]string{
   239  				"label":       "URL Slug",
   240  				"type":        "text",
   241  				"disabled":    "true",
   242  				"placeholder": "Will be set automatically",
   243  			}),
   244  		},
   245  		{
   246  			View: Timestamp("Timestamp", p, map[string]string{
   247  				"type":  "hidden",
   248  				"class": "timestamp __ponzu",
   249  			}),
   250  		},
   251  		{
   252  			View: Timestamp("Updated", p, map[string]string{
   253  				"type":  "hidden",
   254  				"class": "updated __ponzu",
   255  			}),
   256  		},
   257  	}
   258  
   259  	for _, f := range defaults {
   260  		err := addFieldToEditorView(e, f)
   261  		if err != nil {
   262  			return err
   263  		}
   264  	}
   265  
   266  	return nil
   267  }