UNPKG

confluence-tool

Version:
283 lines (240 loc) 7.87 kB
cheerio = require 'cheerio' {isHtml} = require './util' transformNamespaces = (src,dst,elements) -> re = new RegExp("(.*)#{src}(.*)") elements.each -> if mob = @name.match re @name = mob[1] + dst + mob[2] if mob[1] not in knownNamespaces knownNamespaces.push mob[1] keys = (k for k of @attribs) for k in keys if mob = k.match re name = mob[1] + dst + mob[2] @attribs[name] = @attribs[k] delete @attribs[k] if mob[1] not in knownNamespaces knownNamespaces.push mob[1] elements fixHtml = (s) -> if isHtml s data = cheerio.load s, xmlMode: true transformNamespaces ":", "--", data('*') data.html() else s cleanHtml = (s) -> data = cheerio.load s, xmlMode: true transformNamespaces "--", "*", data('*') cheerioOrig['html'].call data.root() knownNamespaces = [] # This is a workaround, such that cheerio understands namespace CSS selectors. # cheerioOrig = {} # translate selectors cheerioOrig['find'] = cheerio::find cheerio::find = (selector) -> if typeof selector isnt 'string' debugger #console.log "this", this, "selector", selector, knownNamespaces selector = selector.replace /(\w+)\|(\w+)/g, (m, ns, tag) -> "#{ns}--#{tag}" # if ns in knownNamespaces # "#{ns}--#{tag}" # else # m #console.log "selector after", selector cheerioOrig['find'].call this, selector cheerioOrig['attr'] = cheerio::attr cheerio::attr = (name, value) -> name = name.replace ":", "--" #name = name.replace "|", "--" cheerioOrig['attr'].call this, name, value # make sure, that new HTML content is also internally transformed to be # selectible with namespace CSS selectors for method in ['append', 'prepend', 'after', 'before', 'replaceWith'] do (method) -> cheerioOrig[method] = cheerio::[method] cheerio::[method] = (args...) -> args = args.map (arg) -> fixHtml arg cheerioOrig[method].call this, args... for method in ['html'] do (method) -> cheerioOrig[method] = cheerio::[method] cheerio::[method] = (args...) -> if args.length args = args.map (arg) -> fixHtml arg # elem = this # if not elem[0] and not elem.children # elem = elem.root() result = cheerioOrig[method].apply this, args if typeof result is 'string' cleanHtml result else result # Public: provide a jQuery-like storage-format editor # # ```coffee # {StorageEditor} = require 'atlassian-confluence-api' # editor = new StorageEditor # $ = editor.beginEdit(content) # # append a '.' to all paragraphs # $('p').append(".") # newContent = editor.endEdit() # ``` # # This uses cheerio. With the extension, that you can use xml namespace # CSS selectors to select items, e.g. "ac|structured-macro" or "ri|link". # For doing this, you must start your edit with editor's beginEdit() method # and get the data back with endEdit(). # class StorageEditor constructor: -> @handlebars = require 'handlebars' @templates = {} @initialize() # Public: get only a jquery object @jQuery: (content) -> (new StorageEditor).beginEdit(content) addTemplate: (name, template) -> @templates[name] = @handlebars.compile template applyTemplate: (name, data) -> @templates[name](data) addPartial: (name, template) -> @templates[name] = @handlebars.compile "{{> #{name}}}" @handlebars.registerPartial(name, template) addDecorator: (name, decorator) -> @handlebars.registerDecorator(name, decorator) addHelper: (name, helper) -> @handlebars.registerHelper(name, helper) # Private: initialize data # # override this method to do some initializations, if you inherit from # StorageEditor. initialize: -> # Public: override this for editing content # # This method will be called from an editor passed to {ConfluenceAPI::editPage}. # You can change content or title. # # page - {Object} # :body - {Object} # :storage - {Object} # :value - {String} storage representation # # :spaceKey - {String} Space key of page to edit # :title - {String} Title of page to edit # options - {Object} optional can be ignored if overridden. The original # edit may have have a field `edit`, which contains an array of following # {Object}: # # * `templates` - a list of template data: # * `name` - name of thing # * `type` - one of `template`, `partial`, `decorator`, `helper` # * `data` - data corresponding to type # # Partials can also be used as templates. # * `template` - name of template to be applied to accompanied `data` to # set `content` of this action # * `data` - passed to template # * `select` - CSS selector # * `action` - Action to be run on selector # * `content` - content to be passed to action # # This will result in following jQuery call: # # $(select)[action](content) # # # Examples # # editor.edit content, # select: 'p:last' # action: 'after' # content: "<p>Hello world</p>" # # editor.edit content, # select: 'p:last' # action: 'after' # templates: # name: 'list' # data: ''' # <ul> # {#each items} # <li>{{item}}</li> # {/each} # </ul> # ''' # template: 'list' # data: # items: [ {item: 1}, {item: 2}, {item: 3} ] # # # If you override this method, basic skeleton is: # # class MyEditor extends StorageEditor # edit: (content, options) -> # $ = @beginEdit content # # namespace aware CSS selector # $("ac|structured-macro").replaceWith(...) # # manipulate content here using n # @endEdit() # # Usually # # Returns modified input {Object} or {Promise}, which resolves to it. New # content shall be stored to `newBody` edit: (page, editor) -> if editor if editor.edit $=@beginEdit(page.body.storage.value) if editor.edit instanceof Array editActions = editor.edit else editActions = [ editor.edit ] for editAction in editActions if typeof editAction is 'function' editAction($,@) else {select, action, content, template, templates, data} = editAction templateData = data if templates unless templates instanceof Array templates = [ templates ] for {name, type, data} in templates @['add'+type.replace(/^\w/, (m)->m.toUpperCase())](name, data) if template content = @applyTemplate(template, data: templateData) if select and action and content $(select)[action](content) page.newBody = @endEdit() else page.newBody = page.body.storage return page # Public: prepare content for editing # # This translates xml namespace prefixes for ns:element to ns--element, such # that it is selectable by CSS selectors. # beginEdit: (content) -> if content @$ = cheerio.load(content, xmlMode: true) @transformNamespaces ":", "--" jQueryWrapper = (args...) => #console.log "jQueryWrapper", "this", this, "args", args, "jQuery", @$ if isHtml args[0] data = @$(args[0]) @transformNamespaces ":", "--", data.find('*') data else @$ args... # Public: end editing and return the edited HTML content endEdit: -> @transformNamespaces "--", ":" @$.html() # Private: transforms namespaces transformNamespaces: (src,dst,elements) -> if not elements elements = @$('*') transformNamespaces src, dst, elements module.exports = StorageEditor