Skip to content
Snippets Groups Projects
Select Git revision
  • 5a4776431da2cc4f64b2f049e5f7b0d49cc03433
  • main default protected
  • renovate/django-5.x
  • koma/feature/preference-polling-form
4 results

forms.py

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    domline.js 8.49 KiB
    'use strict';
    
    // THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.domline
    // %APPJET%: import("etherpad.admin.plugins");
    /**
     * Copyright 2009 Google Inc.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS-IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    // requires: top
    // requires: plugins
    // requires: undefined
    
    const Security = require('./security');
    const hooks = require('./pluginfw/hooks');
    const _ = require('./underscore');
    const lineAttributeMarker = require('./linestylefilter').lineAttributeMarker;
    const noop = () => {};
    
    
    const domline = {};
    
    domline.addToLineClass = (lineClass, cls) => {
      // an "empty span" at any point can be used to add classes to
      // the line, using line:className.  otherwise, we ignore
      // the span.
      cls.replace(/\S+/g, (c) => {
        if (c.indexOf('line:') === 0) {
          // add class to line
          lineClass = (lineClass ? `${lineClass} ` : '') + c.substring(5);
        }
      });
      return lineClass;
    };
    
    // if "document" is falsy we don't create a DOM node, just
    // an object with innerHTML and className
    domline.createDomLine = (nonEmpty, doesWrap, optBrowser, optDocument) => {
      const result = {
        node: null,
        appendSpan: noop,
        prepareForAdd: noop,
        notifyAdded: noop,
        clearSpans: noop,
        finishUpdate: noop,
        lineMarker: 0,
      };
    
      const document = optDocument;
    
      if (document) {
        result.node = document.createElement('div');
        // JAWS and NVDA screen reader compatibility. Only needed if in a real browser.
        result.node.setAttribute('aria-live', 'assertive');
      } else {
        result.node = {
          innerHTML: '',
          className: '',
        };
      }
    
      let html = [];
      let preHtml = '';
      let postHtml = '';
      let curHTML = null;
    
      const processSpaces = (s) => domline.processSpaces(s, doesWrap);
      const perTextNodeProcess = (doesWrap ? _.identity : processSpaces);
      const perHtmlLineProcess = (doesWrap ? processSpaces : _.identity);
      let lineClass = 'ace-line';
    
      result.appendSpan = (txt, cls) => {
        let processedMarker = false;
        // Handle lineAttributeMarker, if present
        if (cls.indexOf(lineAttributeMarker) >= 0) {
          let listType = /(?:^| )list:(\S+)/.exec(cls);
          const start = /(?:^| )start:(\S+)/.exec(cls);
    
          _.map(hooks.callAll('aceDomLinePreProcessLineAttributes', {
            domline,
            cls,
          }), (modifier) => {
            preHtml += modifier.preHtml;
            postHtml += modifier.postHtml;
            processedMarker |= modifier.processedMarker;
          });
          if (listType) {
            listType = listType[1];
            if (listType) {
              if (listType.indexOf('number') < 0) {
                preHtml += `<ul class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
                postHtml = `</li></ul>${postHtml}`;
              } else {
                if (start) { // is it a start of a list with more than one item in?
                  if (Number.parseInt(start[1]) === 1) { // if its the first one at this level?
                    // Add start class to DIV node
                    lineClass = `${lineClass} ` + `list-start-${listType}`;
                  }
                  preHtml +=
                    `<ol start=${start[1]} class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
                } else {
                  // Handles pasted contents into existing lists
                  preHtml += `<ol class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
                }
                postHtml += '</li></ol>';
              }
            }
            processedMarker = true;
          }
          _.map(hooks.callAll('aceDomLineProcessLineAttributes', {
            domline,
            cls,
          }), (modifier) => {
            preHtml += modifier.preHtml;
            postHtml += modifier.postHtml;
            processedMarker |= modifier.processedMarker;
          });
          if (processedMarker) {
            result.lineMarker += txt.length;
            return; // don't append any text
          }
        }
        let href = null;
        let simpleTags = null;
        if (cls.indexOf('url') >= 0) {
          cls = cls.replace(/(^| )url:(\S+)/g, (x0, space, url) => {
            href = url;
            return `${space}url`;
          });
        }
        if (cls.indexOf('tag') >= 0) {
          cls = cls.replace(/(^| )tag:(\S+)/g, (x0, space, tag) => {
            if (!simpleTags) simpleTags = [];
            simpleTags.push(tag.toLowerCase());
            return space + tag;
          });
        }
    
        let extraOpenTags = '';
        let extraCloseTags = '';
    
        _.map(hooks.callAll('aceCreateDomLine', {
          domline,
          cls,
        }), (modifier) => {
          cls = modifier.cls;
          extraOpenTags += modifier.extraOpenTags;
          extraCloseTags = modifier.extraCloseTags + extraCloseTags;
        });
    
        if ((!txt) && cls) {
          lineClass = domline.addToLineClass(lineClass, cls);
        } else if (txt) {
          if (href) {
            const urn_schemes = new RegExp('^(about|geo|mailto|tel):');
            // if the url doesn't include a protocol prefix, assume http
            if (!~href.indexOf('://') && !urn_schemes.test(href)) {
              href = `http://${href}`;
            }
            // Using rel="noreferrer" stops leaking the URL/location of the pad when
            // clicking links in the document.
            // Not all browsers understand this attribute, but it's part of the HTML5 standard.
            // https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
            // Additionally, we do rel="noopener" to ensure a higher level of referrer security.
            // https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
            // https://mathiasbynens.github.io/rel-noopener/
            // https://github.com/ether/etherpad-lite/pull/3636
            const escapedHref = Security.escapeHTMLAttribute(href);
            extraOpenTags = `${extraOpenTags}<a href="${escapedHref}" rel="noreferrer noopener">`;
            extraCloseTags = `</a>${extraCloseTags}`;
          }
          if (simpleTags) {
            simpleTags.sort();
            extraOpenTags = `${extraOpenTags}<${simpleTags.join('><')}>`;
            simpleTags.reverse();
            extraCloseTags = `</${simpleTags.join('></')}>${extraCloseTags}`;
          }
          html.push(
              '<span class="', Security.escapeHTMLAttribute(cls || ''),
              '">',
              extraOpenTags,
              perTextNodeProcess(Security.escapeHTML(txt)),
              extraCloseTags,
              '</span>');
        }
      };
      result.clearSpans = () => {
        html = [];
        lineClass = 'ace-line';
        result.lineMarker = 0;
      };
    
      const writeHTML = () => {
        let newHTML = perHtmlLineProcess(html.join(''));
        if (!newHTML) {
          if ((!document) || (!optBrowser)) {
            newHTML += '&nbsp;';
          } else {
            newHTML += '<br/>';
          }
        }
        if (nonEmpty) {
          newHTML = (preHtml || '') + newHTML + (postHtml || '');
        }
        html = preHtml = postHtml = ''; // free memory
        if (newHTML !== curHTML) {
          curHTML = newHTML;
          result.node.innerHTML = curHTML;
        }
        if (lineClass != null) result.node.className = lineClass;
    
        hooks.callAll('acePostWriteDomLineHTML', {
          node: result.node,
        });
      };
      result.prepareForAdd = writeHTML;
      result.finishUpdate = writeHTML;
      return result;
    };
    
    domline.processSpaces = (s, doesWrap) => {
      if (s.indexOf('<') < 0 && !doesWrap) {
        // short-cut
        return s.replace(/ /g, '&nbsp;');
      }
      const parts = [];
      s.replace(/<[^>]*>?| |[^ <]+/g, (m) => {
        parts.push(m);
      });
      if (doesWrap) {
        let endOfLine = true;
        let beforeSpace = false;
        // last space in a run is normal, others are nbsp,
        // end of line is nbsp
        for (let i = parts.length - 1; i >= 0; i--) {
          const p = parts[i];
          if (p === ' ') {
            if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
            endOfLine = false;
            beforeSpace = true;
          } else if (p.charAt(0) !== '<') {
            endOfLine = false;
            beforeSpace = false;
          }
        }
        // beginning of line is nbsp
        for (let i = 0; i < parts.length; i++) {
          const p = parts[i];
          if (p === ' ') {
            parts[i] = '&nbsp;';
            break;
          } else if (p.charAt(0) !== '<') {
            break;
          }
        }
      } else {
        for (let i = 0; i < parts.length; i++) {
          const p = parts[i];
          if (p === ' ') {
            parts[i] = '&nbsp;';
          }
        }
      }
      return parts.join('');
    };
    
    exports.domline = domline;