anchor.diff:          json['numPendingEditSuggestions']);
     }
   
  +  String get url {
  +    var queryStr = id == 65502 ? '?view=template' : '';
  +    var postPath = '/-$pageId$queryStr#post-$id';
  +    return postPath;
  +  }
  +
  +  bool get showNewFlagsLink => numPendingFlags > 0;
  +  bool get showOldFlagsLink => numHandledFlags > 0;
  +
  +  String get description {
  +    var what;
  +    switch(id) {
  +      case 65501: what = 'Page title'; break;
  +      case 65502: what = 'Page'; break;
  +      case 65503: what = 'Page config'; break;
  +      default: what = 'Comment'; break;
  +    }
  +    var text;
  +    switch (status) {
  +      case 'New': text = 'New ${what}'; break; // COULD to lowercase
  +      case 'NewPrelApproved': text = 'New ${what}, prel. approved'; break; // COULD lowercase
  +      case 'Approved': text = what; break;
  +      case 'Rejected': text = '$what, rejected'; break;
  +      case 'EditsRejected': text = '$what, edits rejected'; break;
  +      case 'NewEdits': text = '$what, edited'; break;
  +      case 'EditsPrelApproved': text = '$what, edits prel. approved'; break;
  +      default: text = '$what, ${status}'; break;
  +    }
  +    return text;
  +  }
  +
  +  String get textOrDiff {
  +    var text = '';
  +    switch (status) {
  +      case 'New':
  +        text = _escapeHtml(unapprovedText);
  +        break;
  +      case 'NewPrelApproved':
  +      case 'Approved':
  +        text = _escapeHtml(approvedText);
  +        break;
  +      case 'Rejected':
  +        // Sometimes `unapprovedText` is undefined, nevertheless the post was rejected.
  +        text = unapprovedText != null ? unapprovedText : approvedText;
  +        text = _escapeHtml(text);
  +        break;
  +      case 'EditsRejected':
  +      case 'EditsPrelApproved':
  +      case 'NewEdits':
  +        text = _htmlDiffOfApprovedAndUnapproved();
  +        break;
  +      default:
  +        error('[DwE38RUJ0]');
  +    }
  +    return text;
  +  }
  +
  +  String _htmlDiffOfApprovedAndUnapproved() {
  +    return 'TODO(diff)';
  +    //DiffMatchPatch dmp;
  +    //var diff = dmp.diff_main(approvedText, unapprovedText);
  +    // Could use: `diff_cleanupSemantic diff` instead, but that sometimes
  +    // result in the diff algorithm sometimes replacing too much old text.
  +    //dmp.diff_cleanupEfficiency(diff);
  +    //return diff.toString();
  +    //htmlString = d.i.prettyHtmlFor(diff);
  +    //htmlString
  +  }
  +
  +  /**
  +   * COULD move to util.dart?
  +   */
  +  String _escapeHtml(String html) {
  +    return html;
  +    // TODO:
  +    //html.replace(/&/g, "&amp;")
  +    //.replace(/</g, "&lt;")
  +    //.replace(/>/g, "&gt;")
  +    // Could also replace ' and " if needs to escape attribute value.
  +  }
  +
   }
  
positive.code:   Post(this._debikiData, this.id, this.type, this.pageId, this.loginId, this.userId,
        this.status, this.unapprovedText, this.approvedText,
        this.createdAt, this.numHandledFlags, this.numPendingFlags,
        this.numPendingEditSuggestions);
  
    String toJsonString() {
      Map data = {
  
negative.code: 
  import 'dart:convert';
  
  //import '../diff-match-patch/DiffMatchPatch.dart';
  import '../util.dart';
  import 'debiki_data.dart';
  import 'topic.dart';
  import 'user.dart';
  
  
  /**
   * Is e.g. a comment, or a page title or page body or forum topic.
   *
   * Could perhaps split into class Post and class PostView, the former would
   * contain domain model data, the latter would contain view related methods
   * but no domain model data.
   */
  class Post {
  
    DebikiData _debikiData;
  