fulian23 2 months ago
commit
073b1149b6
100 changed files with 6645 additions and 0 deletions
  1. 4 0
      .gitignore
  2. 110 0
      app/__init__.py
  3. 31 0
      app/constants.py
  4. 801 0
      app/lib/yuntongxun/CCPRestSDK.py
  5. 0 0
      app/lib/yuntongxun/__init__.py
  6. 55 0
      app/lib/yuntongxun/cpp.py
  7. 75 0
      app/lib/yuntongxun/sms.py
  8. 170 0
      app/lib/yuntongxun/xmltojson.py
  9. 198 0
      app/models.py
  10. 0 0
      app/modules/__init__.py
  11. 17 0
      app/modules/admin/__init__.py
  12. 284 0
      app/modules/admin/views.py
  13. 5 0
      app/modules/index/__init__.py
  14. 106 0
      app/modules/index/views.py
  15. 5 0
      app/modules/news/__init__.py
  16. 226 0
      app/modules/news/views.py
  17. 6 0
      app/modules/passport/__init__.py
  18. 254 0
      app/modules/passport/views.py
  19. 5 0
      app/modules/profile/__init__.py
  20. 307 0
      app/modules/profile/views.py
  21. 27 0
      app/static/admin/css/jquery.pagination.css
  22. 633 0
      app/static/admin/css/main.css
  23. 54 0
      app/static/admin/css/reset.css
  24. 130 0
      app/static/admin/html/news_edit.html
  25. 59 0
      app/static/admin/html/news_edit_detail.html
  26. 70 0
      app/static/admin/html/news_type.html
  27. BIN
      app/static/admin/images/icons.png
  28. BIN
      app/static/admin/images/login-bg.jpg
  29. BIN
      app/static/admin/images/logo.png
  30. BIN
      app/static/admin/images/person.png
  31. 0 0
      app/static/admin/js/echarts.min.js
  32. 1 0
      app/static/admin/js/jquery-1.12.4.min.js
  33. 10 0
      app/static/admin/js/jquery.form.min.js
  34. 0 0
      app/static/admin/js/jquery.pagination.min.js
  35. 16 0
      app/static/admin/js/news_edit_detail.js
  36. 60 0
      app/static/admin/js/news_review_detail.js
  37. 73 0
      app/static/admin/js/news_type.js
  38. 34 0
      app/static/admin/js/tinymce_setup.js
  39. 504 0
      app/static/admin/tinymce/LICENSE.TXT
  40. 916 0
      app/static/admin/tinymce/changelog.txt
  41. 0 0
      app/static/admin/tinymce/js/tinymce/jquery.tinymce.min.js
  42. 3 0
      app/static/admin/tinymce/js/tinymce/langs/readme.md
  43. 260 0
      app/static/admin/tinymce/js/tinymce/langs/zh_CN.js
  44. 504 0
      app/static/admin/tinymce/js/tinymce/license.txt
  45. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/advlist/plugin.min.js
  46. 1 0
      app/static/admin/tinymce/js/tinymce/plugins/anchor/plugin.min.js
  47. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/autolink/plugin.min.js
  48. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/autoresize/plugin.min.js
  49. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/autosave/plugin.min.js
  50. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/bbcode/plugin.min.js
  51. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/charmap/plugin.min.js
  52. 1 0
      app/static/admin/tinymce/js/tinymce/plugins/code/plugin.min.js
  53. 138 0
      app/static/admin/tinymce/js/tinymce/plugins/codesample/css/prism.css
  54. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/codesample/plugin.min.js
  55. 1 0
      app/static/admin/tinymce/js/tinymce/plugins/colorpicker/plugin.min.js
  56. 1 0
      app/static/admin/tinymce/js/tinymce/plugins/contextmenu/plugin.min.js
  57. 1 0
      app/static/admin/tinymce/js/tinymce/plugins/directionality/plugin.min.js
  58. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-cool.gif
  59. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-cry.gif
  60. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-embarassed.gif
  61. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-foot-in-mouth.gif
  62. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-frown.gif
  63. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-innocent.gif
  64. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-kiss.gif
  65. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-laughing.gif
  66. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-money-mouth.gif
  67. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-sealed.gif
  68. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-smile.gif
  69. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-surprised.gif
  70. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-tongue-out.gif
  71. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-undecided.gif
  72. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-wink.gif
  73. BIN
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-yell.gif
  74. 1 0
      app/static/admin/tinymce/js/tinymce/plugins/emoticons/plugin.min.js
  75. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/fullpage/plugin.min.js
  76. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/fullscreen/plugin.min.js
  77. BIN
      app/static/admin/tinymce/js/tinymce/plugins/help/img/logo.png
  78. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/help/plugin.min.js
  79. 1 0
      app/static/admin/tinymce/js/tinymce/plugins/hr/plugin.min.js
  80. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/image/plugin.min.js
  81. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/imagetools/plugin.min.js
  82. 82 0
      app/static/admin/tinymce/js/tinymce/plugins/imageupload/css/style.css
  83. BIN
      app/static/admin/tinymce/js/tinymce/plugins/imageupload/img/icon.png
  84. 91 0
      app/static/admin/tinymce/js/tinymce/plugins/imageupload/jquery.iframe-post-form.js
  85. 89 0
      app/static/admin/tinymce/js/tinymce/plugins/imageupload/plugin.min.js
  86. 82 0
      app/static/admin/tinymce/js/tinymce/plugins/imageupload/uncompressed/css/style.css
  87. 138 0
      app/static/admin/tinymce/js/tinymce/plugins/imageupload/uncompressed/plugin.js
  88. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/importcss/plugin.min.js
  89. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/insertdatetime/plugin.min.js
  90. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/legacyoutput/plugin.min.js
  91. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/link/plugin.min.js
  92. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/lists/plugin.min.js
  93. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/media/plugin.min.js
  94. 1 0
      app/static/admin/tinymce/js/tinymce/plugins/nonbreaking/plugin.min.js
  95. 1 0
      app/static/admin/tinymce/js/tinymce/plugins/noneditable/plugin.min.js
  96. 1 0
      app/static/admin/tinymce/js/tinymce/plugins/pagebreak/plugin.min.js
  97. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/paste/plugin.min.js
  98. 0 0
      app/static/admin/tinymce/js/tinymce/plugins/preview/plugin.min.js
  99. 1 0
      app/static/admin/tinymce/js/tinymce/plugins/print/plugin.min.js
  100. 1 0
      app/static/admin/tinymce/js/tinymce/plugins/save/plugin.min.js

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+.idea
+py[cod]
+logs/log*
+migrations

+ 110 - 0
app/__init__.py

@@ -0,0 +1,110 @@
+import logging
+from logging.handlers import RotatingFileHandler
+
+from redis import StrictRedis
+from flask import Flask, g, render_template
+from flask_sqlalchemy import SQLAlchemy
+from flask_session import Session
+
+from flask_wtf.csrf import CSRFProtect, generate_csrf
+
+from app.utils.common import do_index_class, user_login_data
+from config import config_dict
+
+db = SQLAlchemy()
+redis_store = None  # type:StrictRedis
+
+
+def setup_log(config_name):
+    """配置日志"""
+    # 设置日志的记录等级
+    logging.basicConfig(level=config_dict[config_name].LOG_LEVEL)  # 调试debug级
+    # 创建日志记录器,指明日志保存的路径、每个日志文件的最大大小、保存的日志文件个数上限
+    file_log_handler = RotatingFileHandler("logs/log", maxBytes=1024 * 1024 * 100, backupCount=10)
+    # 创建日志记录的格式 日志等级 输入日志信息的文件名 行数 日志信息
+    formatter = logging.Formatter('%(levelname)s %(filename)s:%(lineno)d %(message)s')
+    # 为刚创建的日志记录器设置日志记录格式
+    file_log_handler.setFormatter(formatter)
+    # 为全局的日志工具对象(flask app使用的)添加日志记录器
+    logging.getLogger().addHandler(file_log_handler)
+
+
+def create_app(config_name):
+    """通过传入不同的配置名字,初始化其对应配置的应用实例"""
+    app = Flask(__name__)
+    setup_log(config_name)
+
+    # 设置app应用的config配置
+    app.config.from_object(config_dict[config_name])
+    # 构建数据库对象
+    db.init_app(app)
+    # 构建redis数据库对象
+    global redis_store
+    redis_store = StrictRedis(host=config_dict[config_name].REDIS_HOST,
+                              port=config_dict[config_name].REDIS_PORT,
+                              decode_responses=True)
+
+    # 开启csrf保护
+    CSRFProtect(app)
+
+    # 每个post请求带上csrf_token参数
+    @app.after_request
+    def after_request(response):
+        # 调用函数生成csrf_token
+        csrf_token = generate_csrf()
+        # 通过cookie将值传给前端
+        response.set_cookie("csrf_token", csrf_token)
+        return response
+
+    @app.errorhandler(404)
+    @user_login_data
+    def page_not_found(_):
+        user = g.user
+        data = {"user_info": user.to_dict() if user else None}
+        return render_template("news/404.html", data=data)
+
+    # 设置session保存位置
+    Session(app)
+
+    # 添加自定义过滤器
+    app.add_template_filter(do_index_class, "index_class")
+
+    # 首页蓝图注册
+    register_index(app)
+    # 登录注册模块的蓝图注册
+    register_passport(app)
+    # 新闻模块的蓝图注册
+    register_news(app)
+    # 个人中心的蓝图注册
+    register_profile(app)
+    # 管理员后台注册
+    register_admin(app)
+
+    return app
+
+
+def register_passport(app):
+    from app.modules.passport import passport_bp
+    app.register_blueprint(passport_bp)
+
+
+def register_index(app):
+    # 注册蓝图
+    from app.modules.index import index_bp
+    app.register_blueprint(index_bp)
+
+
+def register_news(app):
+    # 注册蓝图
+    from app.modules.news import news_bp
+    app.register_blueprint(news_bp)
+
+
+def register_profile(app):
+    from app.modules.profile import profile_bp
+    app.register_blueprint(profile_bp)
+
+
+def register_admin(app):
+    from app.modules.admin import admin_bp
+    app.register_blueprint(admin_bp)

+ 31 - 0
app/constants.py

@@ -0,0 +1,31 @@
+# 图片验证码Redis有效期, 单位:秒
+IMAGE_CODE_REDIS_EXPIRES = 300
+
+# 短信验证码Redis有效期,单位:秒
+SMS_CODE_REDIS_EXPIRES = 300
+
+# 七牛空间域名
+QINIU_DOMIN_PREFIX = "http://oz6itywx9.bkt.clouddn.com/"
+
+# 首页展示最多的新闻数量
+HOME_PAGE_MAX_NEWS = 10
+
+# 用户的关注每一页最多数量
+USER_FOLLOWED_MAX_COUNT = 4
+
+# 用户收藏最多新闻数量
+USER_COLLECTION_MAX_NEWS = 4
+
+# 其他用户每一页最多新闻数量
+OTHER_NEWS_PAGE_MAX_COUNT = 1
+
+# 点击排行展示的最多新闻数据
+CLICK_RANK_MAX_NEWS = 6
+
+# 管理员页面用户每页多最数据条数
+ADMIN_USER_PAGE_MAX_COUNT = 10
+
+# 管理员页面新闻每页最多数据条数
+ADMIN_NEWS_PAGE_MAX_COUNT = 10
+
+

+ 801 - 0
app/lib/yuntongxun/CCPRestSDK.py

@@ -0,0 +1,801 @@
+# -*- coding: UTF-8 -*-
+#  Copyright (c) 2014 The CCP project authors. All Rights Reserved.
+#
+#  Use of this source code is governed by a Beijing Speedtong Information Technology Co.,Ltd license
+#  that can be found in the LICENSE file in the root of the web site.
+#
+#   http://www.yuntongxun.com
+#
+#  An additional intellectual property rights grant can be found
+#  in the file PATENTS.  All contributing project authors may
+#  be found in the AUTHORS file in the root of the source tree.
+
+from hashlib import md5
+import base64
+import datetime
+from urllib import request as urllib2
+import json
+from .xmltojson import xmltojson
+
+
+class REST:
+    AccountSid = ''
+    AccountToken = ''
+    AppId = ''
+    SubAccountSid = ''
+    SubAccountToken = ''
+    ServerIP = ''
+    ServerPort = ''
+    SoftVersion = ''
+    Iflog = False  # 是否打印日志
+    Batch = ''  # 时间戳
+    BodyType = 'xml'  # 包体格式,可填值:json 、xml
+
+    # 初始化
+    # @param serverIP       必选参数    服务器地址
+    # @param serverPort     必选参数    服务器端口
+    # @param softVersion    必选参数    REST版本号
+    def __init__(self, ServerIP, ServerPort, SoftVersion):
+
+        self.ServerIP = ServerIP
+        self.ServerPort = ServerPort
+        self.SoftVersion = SoftVersion
+
+    # 设置主帐号
+    # @param AccountSid  必选参数    主帐号
+    # @param AccountToken  必选参数    主帐号Token
+
+    def setAccount(self, AccountSid, AccountToken):
+        self.AccountSid = AccountSid
+        self.AccountToken = AccountToken
+
+    # 设置子帐号
+    # 
+    # @param SubAccountSid  必选参数    子帐号
+    # @param SubAccountToken  必选参数    子帐号Token
+
+    def setSubAccount(self, SubAccountSid, SubAccountToken):
+        self.SubAccountSid = SubAccountSid
+        self.SubAccountToken = SubAccountToken
+
+    # 设置应用ID
+    # 
+    # @param AppId  必选参数    应用ID
+
+    def setAppId(self, AppId):
+        self.AppId = AppId
+
+    def log(self, url, body, data):
+        print('这是请求的URL:')
+        print(url)
+        print('这是请求包体:')
+        print(body)
+        print('这是响应包体:')
+        print(data)
+        print('********************************')
+
+    # 创建子账号
+    # @param friendlyName   必选参数      子帐号名称
+    def CreateSubAccount(self, friendlyName):
+
+        self.accAuth()
+        nowdate = datetime.datetime.now()
+        self.Batch = nowdate.strftime("%Y%m%d%H%M%S")
+        # 生成sig
+        signature = self.AccountSid + self.AccountToken + self.Batch
+        sig = md5(signature.encode()).hexdigest().upper()
+        # 拼接URL
+        url = "https://" + self.ServerIP + ":" + self.ServerPort + "/" + self.SoftVersion + "/Accounts/" + self.AccountSid + "/SubAccounts?sig=" + sig
+        # 生成auth
+        src = self.AccountSid + ":" + self.Batch
+        auth = base64.encodebytes(src.encode()).decode().strip()
+        req = urllib2.Request(url)
+        self.setHttpHeader(req)
+        req.add_header("Authorization", auth)
+        # xml格式
+        body = '''<?xml version="1.0" encoding="utf-8"?><SubAccount><appId>%s</appId>\
+            <friendlyName>%s</friendlyName>\
+            </SubAccount>\
+            ''' % (self.AppId, friendlyName)
+
+        if self.BodyType == 'json':
+            # json格式
+            body = '''{"friendlyName": "%s", "appId": "%s"}''' % (friendlyName, self.AppId)
+        data = ''
+        req.data = body.encode()
+        try:
+            res = urllib2.urlopen(req)
+            data = res.read()
+            res.close()
+
+            if self.BodyType == 'json':
+                # json格式
+                locations = json.loads(data)
+            else:
+                # xml格式
+                xtj = xmltojson()
+                locations = xtj.main(data)
+            if self.Iflog:
+                self.log(url, body, data)
+            return locations
+        except Exception as error:
+            if self.Iflog:
+                self.log(url, body, data)
+            return {'172001': '网络错误'}
+
+    #  获取子帐号
+    # @param startNo  可选参数    开始的序号,默认从0开始
+    # @param offset 可选参数     一次查询的最大条数,最小是1条,最大是100条
+    def getSubAccounts(self, startNo, offset):
+
+        self.accAuth()
+        nowdate = datetime.datetime.now()
+        self.Batch = nowdate.strftime("%Y%m%d%H%M%S")
+        # 生成sig
+        signature = self.AccountSid + self.AccountToken + self.Batch
+        sig = md5(signature.encode()).hexdigest().upper()
+        # 拼接URL
+        url = "https://" + self.ServerIP + ":" + self.ServerPort + "/" + self.SoftVersion + "/Accounts/" + self.AccountSid + "/GetSubAccounts?sig=" + sig
+        # 生成auth
+        src = self.AccountSid + ":" + self.Batch
+        # auth = base64.encodestring(src).strip()
+        auth = base64.encodebytes(src.encode()).decode().strip()
+        req = urllib2.Request(url)
+        self.setHttpHeader(req)
+        req.add_header("Authorization", auth)
+        # xml格式
+        body = '''<?xml version="1.0" encoding="utf-8"?><SubAccount><appId>%s</appId>\
+            <startNo>%s</startNo><offset>%s</offset>\
+            </SubAccount>\
+            ''' % (self.AppId, startNo, offset)
+
+        if self.BodyType == 'json':
+            # json格式
+            body = '''{"appId": "%s", "startNo": "%s", "offset": "%s"}''' % (self.AppId, startNo, offset)
+        data = ''
+        req.data = body.encode()
+        try:
+            res = urllib2.urlopen(req)
+            data = res.read()
+            res.close()
+
+            if self.BodyType == 'json':
+                # json格式
+                locations = json.loads(data)
+            else:
+                # xml格式
+                xtj = xmltojson()
+                locations = xtj.main(data)
+            if self.Iflog:
+                self.log(url, body, data)
+            return locations
+        except Exception as error:
+            if self.Iflog:
+                self.log(url, body, data)
+            return {'172001': '网络错误'}
+
+    # 子帐号信息查询
+    # @param friendlyName 必选参数   子帐号名称
+
+    def querySubAccount(self, friendlyName):
+
+        self.accAuth()
+        nowdate = datetime.datetime.now()
+        self.Batch = nowdate.strftime("%Y%m%d%H%M%S")
+        # 生成sig
+        signature = self.AccountSid + self.AccountToken + self.Batch
+        sig = md5(signature.encode()).hexdigest().upper()
+        # 拼接URL
+        url = "https://" + self.ServerIP + ":" + self.ServerPort + "/" + self.SoftVersion + "/Accounts/" + self.AccountSid + "/QuerySubAccountByName?sig=" + sig
+        # 生成auth
+        src = self.AccountSid + ":" + self.Batch
+        # auth = base64.encodestring(src).strip()
+        auth = base64.encodebytes(src.encode()).decode().strip()
+        req = urllib2.Request(url)
+        self.setHttpHeader(req)
+
+        req.add_header("Authorization", auth)
+
+        # 创建包体
+        body = '''<?xml version="1.0" encoding="utf-8"?><SubAccount><appId>%s</appId>\
+            <friendlyName>%s</friendlyName>\
+            </SubAccount>\
+            ''' % (self.AppId, friendlyName)
+        if self.BodyType == 'json':
+            body = '''{"friendlyName": "%s", "appId": "%s"}''' % (friendlyName, self.AppId)
+        data = ''
+        req.data = body.encode()
+        try:
+            res = urllib2.urlopen(req)
+            data = res.read()
+            res.close()
+
+            if self.BodyType == 'json':
+                # json格式
+                locations = json.loads(data)
+            else:
+                # xml格式
+                xtj = xmltojson()
+                locations = xtj.main(data)
+            if self.Iflog:
+                self.log(url, body, data)
+            return locations
+        except Exception as error:
+            if self.Iflog:
+                self.log(url, body, data)
+            return {'172001': '网络错误'}
+
+    # 发送模板短信
+    # @param to  必选参数     短信接收彿手机号码集合,用英文逗号分开
+    # @param datas 可选参数    内容数据
+    # @param tempId 必选参数    模板Id
+    def sendTemplateSMS(self, to, datas, tempId):
+
+        self.accAuth()
+        nowdate = datetime.datetime.now()
+        self.Batch = nowdate.strftime("%Y%m%d%H%M%S")
+        # 生成sig
+        signature = self.AccountSid + self.AccountToken + self.Batch
+        sig = md5(signature.encode()).hexdigest().upper()
+        # 拼接URL
+        url = "https://" + self.ServerIP + ":" + self.ServerPort + "/" + self.SoftVersion + "/Accounts/" + self.AccountSid + "/SMS/TemplateSMS?sig=" + sig
+        # 生成auth
+        src = self.AccountSid + ":" + self.Batch
+        # auth = base64.encodestring(src).strip()
+        auth = base64.encodebytes(src.encode()).decode().strip()
+        req = urllib2.Request(url)
+        self.setHttpHeader(req)
+        req.add_header("Authorization", auth)
+        # 创建包体
+        b = ''
+        for a in datas:
+            b += '<data>%s</data>' % (a)
+
+        body = '<?xml version="1.0" encoding="utf-8"?><SubAccount><datas>' + b + '</datas><to>%s</to><templateId>%s</templateId><appId>%s</appId>\
+            </SubAccount>\
+            ' % (to, tempId, self.AppId)
+        if self.BodyType == 'json':
+            # if this model is Json ..then do next code
+            b = '['
+            for a in datas:
+                b += '"%s",' % (a)
+            b += ']'
+            body = '''{"to": "%s", "datas": %s, "templateId": "%s", "appId": "%s"}''' % (to, b, tempId, self.AppId)
+        req.data = body.encode()
+        data = ''
+        try:
+            res = urllib2.urlopen(req)
+            data = res.read()
+            res.close()
+
+            if self.BodyType == 'json':
+                # json格式
+                locations = json.loads(data)
+            else:
+                # xml格式
+                xtj = xmltojson()
+                locations = xtj.main(data)
+            if self.Iflog:
+                self.log(url, body, data)
+            return locations
+        except Exception as error:
+            if self.Iflog:
+                self.log(url, body, data)
+            return {'172001': '网络错误'}
+
+    # 外呼通知
+    # @param to 必选参数    被叫号码
+    # @param mediaName 可选参数    语音文件名称,格式 wav。与mediaTxt不能同时为空。当不为空时mediaTxt属性失效。
+    # @param mediaTxt 可选参数    文本内容
+    # @param displayNum 可选参数    显示的主叫号码
+    # @param playTimes 可选参数    循环播放次数,1-3次,默认播放1次。
+    # @param respUrl 可选参数    外呼通知状态通知回调地址,云通讯平台将向该Url地址发送呼叫结果通知。
+    # @param userData 可选参数    用户私有数据
+    # @param maxCallTime 可选参数    最大通话时长
+    # @param speed 可选参数    发音速度
+    # @param volume 可选参数    音量
+    # @param pitch 可选参数    音调
+    # @param bgsound 可选参数    背景音编号
+
+    def landingCall(self, to, mediaName, mediaTxt, displayNum, playTimes, respUrl, userData, maxCallTime, speed, volume,
+                    pitch, bgsound):
+
+        self.accAuth()
+        nowdate = datetime.datetime.now()
+        self.Batch = nowdate.strftime("%Y%m%d%H%M%S")
+        # 生成sig
+        signature = self.AccountSid + self.AccountToken + self.Batch
+        sig = md5(signature.encode()).hexdigest().upper()
+        # 拼接URL
+        url = "https://" + self.ServerIP + ":" + self.ServerPort + "/" + self.SoftVersion + "/Accounts/" + self.AccountSid + "/Calls/LandingCalls?sig=" + sig
+        # 生成auth
+        src = self.AccountSid + ":" + self.Batch
+        # auth = base64.encodestring(src).strip()
+        auth = base64.encodebytes(src.encode()).decode().strip()
+        req = urllib2.Request(url)
+        self.setHttpHeader(req)
+        req.add_header("Authorization", auth)
+
+        # 创建包体
+        body = '''<?xml version="1.0" encoding="utf-8"?><LandingCall>\
+            <to>%s</to><mediaName>%s</mediaName><mediaTxt>%s</mediaTxt><appId>%s</appId><displayNum>%s</displayNum>\
+            <playTimes>%s</playTimes><respUrl>%s</respUrl><userData>%s</userData><maxCallTime>%s</maxCallTime><speed>%s</speed>
+            <volume>%s</volume><pitch>%s</pitch><bgsound>%s</bgsound></LandingCall>\
+            ''' % (
+        to, mediaName, mediaTxt, self.AppId, displayNum, playTimes, respUrl, userData, maxCallTime, speed, volume,
+        pitch, bgsound)
+        if self.BodyType == 'json':
+            body = '''{"to": "%s", "mediaName": "%s","mediaTxt": "%s","appId": "%s","displayNum": "%s","playTimes": "%s","respUrl": "%s","userData": "%s","maxCallTime": "%s","speed": "%s","volume": "%s","pitch": "%s","bgsound": "%s"}''' % (
+            to, mediaName, mediaTxt, self.AppId, displayNum, playTimes, respUrl, userData, maxCallTime, speed, volume,
+            pitch, bgsound)
+        req.data = body.encode()
+        data = ''
+        try:
+            res = urllib2.urlopen(req)
+            data = res.read()
+            res.close()
+
+            if self.BodyType == 'json':
+                # json格式
+                locations = json.loads(data)
+            else:
+                # xml格式
+                xtj = xmltojson()
+                locations = xtj.main(data)
+            if self.Iflog:
+                self.log(url, body, data)
+            return locations
+        except Exception as error:
+            if self.Iflog:
+                self.log(url, body, data)
+            return {'172001': '网络错误'}
+
+    # 语音验证码
+    # @param verifyCode  必选参数   验证码内容,为数字和英文字母,不区分大小写,长度4-8位
+    # @param playTimes  可选参数   播放次数,1-3次
+    # @param to 必选参数    接收号码
+    # @param displayNum 可选参数    显示的主叫号码
+    # @param respUrl 可选参数    语音验证码状态通知回调地址,云通讯平台将向该Url地址发送呼叫结果通知
+    # @param lang 可选参数    语言类型
+    # @param userData 可选参数    第三方私有数据
+
+    def voiceVerify(self, verifyCode, playTimes, to, displayNum, respUrl, lang, userData):
+
+        self.accAuth()
+        nowdate = datetime.datetime.now()
+        self.Batch = nowdate.strftime("%Y%m%d%H%M%S")
+        # 生成sig
+        signature = self.AccountSid + self.AccountToken + self.Batch
+        sig = md5(signature.encode()).hexdigest().upper()
+        # 拼接URL
+        url = "https://" + self.ServerIP + ":" + self.ServerPort + "/" + self.SoftVersion + "/Accounts/" + self.AccountSid + "/Calls/VoiceVerify?sig=" + sig
+        # 生成auth
+        src = self.AccountSid + ":" + self.Batch
+        # auth = base64.encodestring(src).strip()
+        auth = base64.encodebytes(src.encode()).decode().strip()
+        req = urllib2.Request(url)
+        self.setHttpHeader(req)
+
+        req.add_header("Authorization", auth)
+
+        # 创建包体
+        body = '''<?xml version="1.0" encoding="utf-8"?><VoiceVerify>\
+            <appId>%s</appId><verifyCode>%s</verifyCode><playTimes>%s</playTimes><to>%s</to><respUrl>%s</respUrl>\
+            <displayNum>%s</displayNum><lang>%s</lang><userData>%s</userData></VoiceVerify>\
+            ''' % (self.AppId, verifyCode, playTimes, to, respUrl, displayNum, lang, userData)
+        if self.BodyType == 'json':
+            # if this model is Json ..then do next code 
+            body = '''{"appId": "%s", "verifyCode": "%s","playTimes": "%s","to": "%s","respUrl": "%s","displayNum": "%s","lang": "%s","userData": "%s"}''' % (
+            self.AppId, verifyCode, playTimes, to, respUrl, displayNum, lang, userData)
+        req.data = body.encode()
+        data = ''
+        try:
+            res = urllib2.urlopen(req)
+            data = res.read()
+            res.close()
+
+            if self.BodyType == 'json':
+                # json格式
+                locations = json.loads(data)
+            else:
+                # xml格式
+                xtj = xmltojson()
+                locations = xtj.main(data)
+            if self.Iflog:
+                self.log(url, body, data)
+            return locations
+        except Exception as error:
+            if self.Iflog:
+                self.log(url, body, data)
+            return {'172001': '网络错误'}
+
+    # IVR外呼
+    # @param number  必选参数     待呼叫号码,为Dial节点的属性
+    # @param userdata 可选参数    用户数据,在<startservice>通知中返回,只允许填写数字字符,为Dial节点的属性
+    # @param record   可选参数    是否录音,可填项为true和false,默认值为false不录音,为Dial节点的属性
+
+    def ivrDial(self, number, userdata, record):
+
+        self.accAuth()
+        nowdate = datetime.datetime.now()
+        self.Batch = nowdate.strftime("%Y%m%d%H%M%S")
+        # 生成sig
+        signature = self.AccountSid + self.AccountToken + self.Batch;
+        sig = md5(signature.encode()).hexdigest().upper()
+        # 拼接URL
+        url = "https://" + self.ServerIP + ":" + self.ServerPort + "/" + self.SoftVersion + "/Accounts/" + self.AccountSid + "/ivr/dial?sig=" + sig
+        # 生成auth
+        src = self.AccountSid + ":" + self.Batch
+        auth = base64.encodebytes(src.encode()).decode().strip()
+        req = urllib2.Request(url)
+        req.add_header("Accept", "application/xml")
+        req.add_header("Content-Type", "application/xml;charset=utf-8")
+        req.add_header("Authorization", auth)
+
+        # 创建包体
+        body = '''<?xml version="1.0" encoding="utf-8"?>
+                <Request>
+                    <Appid>%s</Appid>
+                    <Dial number="%s"  userdata="%s" record="%s"></Dial>
+                </Request>
+            ''' % (self.AppId, number, userdata, record)
+        req.data = body.encode()
+        data = ''
+        try:
+            res = urllib2.urlopen(req)
+            data = res.read()
+            res.close()
+            xtj = xmltojson()
+            locations = xtj.main(data)
+            if self.Iflog:
+                self.log(url, body, data)
+            return locations
+        except Exception as error:
+            if self.Iflog:
+                self.log(url, body, data)
+            return {'172001': '网络错误'}
+
+    # 话单下载
+    # @param date   必选参数    day 代表前一天的数据(从00:00 – 23:59),目前只支持按天查询
+    # @param keywords  可选参数     客户的查询条件,由客户自行定义并提供给云通讯平台。默认不填忽略此参数
+    def billRecords(self, date, keywords):
+
+        self.accAuth()
+        nowdate = datetime.datetime.now()
+        self.Batch = nowdate.strftime("%Y%m%d%H%M%S")
+        # 生成sig
+        signature = self.AccountSid + self.AccountToken + self.Batch
+        sig = md5(signature.encode()).hexdigest().upper()
+        # 拼接URL
+        url = "https://" + self.ServerIP + ":" + self.ServerPort + "/" + self.SoftVersion + "/Accounts/" + self.AccountSid + "/BillRecords?sig=" + sig
+        # 生成auth
+        src = self.AccountSid + ":" + self.Batch
+        auth = base64.encodebytes(src.encode()).decode().strip()
+        req = urllib2.Request(url)
+        self.setHttpHeader(req)
+        req.add_header("Authorization", auth)
+
+        # 创建包体
+        body = '''<?xml version="1.0" encoding="utf-8"?><BillRecords>\
+            <appId>%s</appId><date>%s</date><keywords>%s</keywords>\
+            </BillRecords>\
+            ''' % (self.AppId, date, keywords)
+        if self.BodyType == 'json':
+            # if this model is Json ..then do next code 
+            body = '''{"appId": "%s", "date": "%s","keywords": "%s"}''' % (self.AppId, date, keywords)
+        req.data = body.encode()
+        data = ''
+        try:
+            res = urllib2.urlopen(req)
+            data = res.read()
+
+            res.close()
+
+            if self.BodyType == 'json':
+                # json格式
+                locations = json.loads(data)
+            else:
+                # xml格式
+                xtj = xmltojson()
+                locations = xtj.main(data)
+            if self.Iflog:
+                self.log(url, body, data)
+            return locations
+        except Exception as error:
+            if self.Iflog:
+                self.log(url, body, data)
+            return {'172001': '网络错误'}
+
+    # 主帐号信息查询
+
+    def queryAccountInfo(self):
+
+        self.accAuth()
+        nowdate = datetime.datetime.now()
+        self.Batch = nowdate.strftime("%Y%m%d%H%M%S")
+        # 生成sig
+        signature = self.AccountSid + self.AccountToken + self.Batch
+        sig = md5(signature.encode()).hexdigest().upper()
+        # 拼接URL
+        url = "https://" + self.ServerIP + ":" + self.ServerPort + "/" + self.SoftVersion + "/Accounts/" + self.AccountSid + "/AccountInfo?sig=" + sig
+        # 生成auth
+        src = self.AccountSid + ":" + self.Batch
+        auth = base64.encodebytes(src.encode()).decode().strip()
+        req = urllib2.Request(url)
+        self.setHttpHeader(req)
+        body = ''
+        req.add_header("Authorization", auth)
+        data = ''
+        try:
+            res = urllib2.urlopen(req)
+            data = res.read()
+            res.close()
+
+            if self.BodyType == 'json':
+                # json格式
+                locations = json.loads(data)
+            else:
+                # xml格式
+                xtj = xmltojson()
+                locations = xtj.main(data)
+            if self.Iflog:
+                self.log(url, body, data)
+            return locations
+        except Exception as error:
+            if self.Iflog:
+                self.log(url, body, data)
+            return {'172001': '网络错误'}
+
+    # 短信模板查询
+    # @param templateId  必选参数   模板Id,不带此参数查询全部可用模板 
+
+    def QuerySMSTemplate(self, templateId):
+
+        self.accAuth()
+        nowdate = datetime.datetime.now()
+        self.Batch = nowdate.strftime("%Y%m%d%H%M%S")
+        # 生成sig
+        signature = self.AccountSid + self.AccountToken + self.Batch
+        sig = md5(signature.encode()).hexdigest().upper()
+        # 拼接URL
+        url = "https://" + self.ServerIP + ":" + self.ServerPort + "/" + self.SoftVersion + "/Accounts/" + self.AccountSid + "/SMS/QuerySMSTemplate?sig=" + sig
+        # 生成auth
+        src = self.AccountSid + ":" + self.Batch
+        auth = base64.encodebytes(src.encode()).decode().strip()
+        req = urllib2.Request(url)
+        self.setHttpHeader(req)
+
+        req.add_header("Authorization", auth)
+
+        # 创建包体
+        body = '''<?xml version="1.0" encoding="utf-8"?><Request>\
+            <appId>%s</appId><templateId>%s</templateId></Request>
+            ''' % (self.AppId, templateId)
+        if self.BodyType == 'json':
+            # if this model is Json ..then do next code 
+            body = '''{"appId": "%s", "templateId": "%s"}''' % (self.AppId, templateId)
+        req.data = body.encode()
+        data = ''
+        try:
+            res = urllib2.urlopen(req)
+            data = res.read()
+            res.close()
+
+            if self.BodyType == 'json':
+                # json格式
+                locations = json.loads(data)
+            else:
+                # xml格式
+                xtj = xmltojson()
+                locations = xtj.main2(data)
+            if self.Iflog:
+                self.log(url, body, data)
+            return locations
+        except Exception as error:
+            if self.Iflog:
+                self.log(url, body, data)
+            return {'172001': '网络错误'}
+
+    # 呼叫结果查询
+    # @param callsid   必选参数    呼叫ID
+
+    def CallResult(self, callSid):
+
+        self.accAuth()
+        nowdate = datetime.datetime.now()
+        self.Batch = nowdate.strftime("%Y%m%d%H%M%S")
+        # 生成sig
+        signature = self.AccountSid + self.AccountToken + self.Batch
+        sig = md5(signature.encode()).hexdigest().upper()
+        # 拼接URL
+        url = "https://" + self.ServerIP + ":" + self.ServerPort + "/" + self.SoftVersion + "/Accounts/" + self.AccountSid + "/CallResult?sig=" + sig + "&callsid=" + callSid
+        # 生成auth
+        src = self.AccountSid + ":" + self.Batch
+        auth = base64.encodebytes(src.encode()).decode().strip()
+        req = urllib2.Request(url)
+        self.setHttpHeader(req)
+        body = ''
+        req.add_header("Authorization", auth)
+        data = ''
+        try:
+            res = urllib2.urlopen(req)
+            data = res.read()
+            res.close()
+
+            if self.BodyType == 'json':
+                # json格式
+                locations = json.loads(data)
+            else:
+                # xml格式
+                xtj = xmltojson()
+                locations = xtj.main(data)
+            if self.Iflog:
+                self.log(url, body, data)
+            return locations
+        except Exception as error:
+            if self.Iflog:
+                self.log(url, body, data)
+            return {'172001': '网络错误'}
+
+    # 呼叫状态查询
+    # @param callid   必选参数    一个由32个字符组成的电话唯一标识符
+    # @param action      可选参数     查询结果通知的回调url地址 
+    def QueryCallState(self, callid, action):
+
+        self.accAuth()
+        nowdate = datetime.datetime.now()
+        self.Batch = nowdate.strftime("%Y%m%d%H%M%S")
+        # 生成sig
+        signature = self.AccountSid + self.AccountToken + self.Batch
+        sig = md5(signature.encode()).hexdigest().upper()
+        # 拼接URL
+        url = "https://" + self.ServerIP + ":" + self.ServerPort + "/" + self.SoftVersion + "/Accounts/" + self.AccountSid + "/ivr/call?sig=" + sig + "&callid=" + callid
+        # 生成auth
+        src = self.AccountSid + ":" + self.Batch
+        auth = base64.encodebytes(src.encode()).decode().strip()
+        req = urllib2.Request(url)
+        self.setHttpHeader(req)
+        req.add_header("Authorization", auth)
+
+        # 创建包体
+        body = '''<?xml version="1.0" encoding="utf-8"?><Request>\
+            <Appid>%s</Appid><QueryCallState callid="%s" action="%s"/>\
+            </Request>\
+            ''' % (self.AppId, callid, action)
+        if self.BodyType == 'json':
+            # if this model is Json ..then do next code 
+            body = '''{"Appid":"%s","QueryCallState":{"callid":"%s","action":"%s"}}''' % (self.AppId, callid, action)
+        req.data = body.encode()
+        data = ''
+        try:
+            res = urllib2.urlopen(req)
+            data = res.read()
+
+            res.close()
+
+            if self.BodyType == 'json':
+                # json格式
+                locations = json.loads(data)
+            else:
+                # xml格式
+                xtj = xmltojson()
+                locations = xtj.main(data)
+            if self.Iflog:
+                self.log(url, body, data)
+            return locations
+        except Exception as error:
+            if self.Iflog:
+                self.log(url, body, data)
+            return {'172001': '网络错误'}
+
+    # 语音文件上传
+    # @param filename   必选参数    文件名
+    # @param body      必选参数     二进制串
+    def MediaFileUpload(self, filename, body):
+
+        self.accAuth()
+        nowdate = datetime.datetime.now()
+        self.Batch = nowdate.strftime("%Y%m%d%H%M%S")
+        # 生成sig
+        signature = self.AccountSid + self.AccountToken + self.Batch
+        sig = md5(signature.encode()).hexdigest().upper()
+        # 拼接URL
+        url = "https://" + self.ServerIP + ":" + self.ServerPort + "/" + self.SoftVersion + "/Accounts/" + self.AccountSid + "/Calls/MediaFileUpload?sig=" + sig + "&appid=" + self.AppId + "&filename=" + filename
+        # 生成auth
+        src = self.AccountSid + ":" + self.Batch
+        auth = base64.encodebytes(src.encode()).decode().strip()
+        req = urllib2.Request(url)
+        req.add_header("Authorization", auth)
+        if self.BodyType == 'json':
+            req.add_header("Accept", "application/json")
+            req.add_header("Content-Type", "application/octet-stream")
+
+        else:
+            req.add_header("Accept", "application/xml")
+            req.add_header("Content-Type", "application/octet-stream")
+
+        # 创建包体
+        req.data = body.encode()
+
+        try:
+            res = urllib2.urlopen(req)
+            data = res.read()
+
+            res.close()
+
+            if self.BodyType == 'json':
+                # json格式
+                locations = json.loads(data)
+            else:
+                # xml格式
+                xtj = xmltojson()
+                locations = xtj.main(data)
+            if self.Iflog:
+                self.log(url, body, data)
+            return locations
+        except Exception as error:
+            if self.Iflog:
+                self.log(url, body, data)
+            return {'172001': '网络错误'}
+
+    # 子帐号鉴权
+    def subAuth(self):
+        if (self.ServerIP == ""):
+            print('172004')
+            print('IP为空')
+
+        if (int(self.ServerPort) <= 0):
+            print('172005')
+            print('端口错误(小于等于0)')
+
+        if (self.SoftVersion == ""):
+            print('172013')
+            print('版本号为空')
+
+        if (self.SubAccountSid == ""):
+            print('172008')
+            print('子帐号为空')
+
+        if (self.SubAccountToken == ""):
+            print('172009')
+            print('子帐号令牌为空')
+
+        if (self.AppId == ""):
+            print('172012')
+            print('应用ID为空')
+
+    # 主帐号鉴权
+    def accAuth(self):
+        if (self.ServerIP == ""):
+            print('172004')
+            print('IP为空')
+
+        if (int(self.ServerPort) <= 0):
+            print('172005')
+            print('端口错误(小于等于0)')
+
+        if (self.SoftVersion == ""):
+            print('172013')
+            print('版本号为空')
+
+        if (self.AccountSid == ""):
+            print('172006')
+            print('主帐号为空')
+
+        if (self.AccountToken == ""):
+            print('172007')
+            print('主帐号令牌为空')
+
+        if (self.AppId == ""):
+            print('172012')
+            print('应用ID为空')
+
+    # 设置包头
+    def setHttpHeader(self, req):
+        if self.BodyType == 'json':
+            req.add_header("Accept", "application/json")
+            req.add_header("Content-Type", "application/json;charset=utf-8")
+
+        else:
+            req.add_header("Accept", "application/xml")
+            req.add_header("Content-Type", "application/xml;charset=utf-8")

+ 0 - 0
app/lib/yuntongxun/__init__.py


+ 55 - 0
app/lib/yuntongxun/cpp.py

@@ -0,0 +1,55 @@
+from app.lib.yuntongxun.CCPRestSDK import REST
+import ssl
+
+ssl._create_default_https_context = ssl._create_unverified_context  # 全局取消证书验证
+
+# 说明:主账号,登陆云通讯网站后,可在"控制台-应用"中看到开发者主账号ACCOUNT SID
+accountSid = '8a216da8662360a40166242b579600c2'
+
+# 说明:主账号Token,登陆云通讯网站后,可在控制台-应用中看到开发者主账号AUTH TOKEN
+accountToken = '01c84ad138664c83b370c43609424fef'
+
+# 请使用管理控制台首页的APPID或自己创建应用的APPID
+appId = '8a216da8662360a40166242b57f600c8'
+
+# 说明:请求地址,生产环境配置成app.cloopen.com
+serverIP = 'app.cloopen.com'
+
+# 说明:请求端口 ,生产环境为8883
+serverPort = '8883'
+
+# 说明:REST API版本号保持不变
+softVersion = '2013-12-26'
+
+
+class CCP(object):
+    """发送短信的辅助类"""
+
+    def __new__(cls, *args, **kwargs):
+        # 判断是否存在类属性_instance,_instance是类CCP的唯一对象,即单例
+        if not hasattr(CCP, "instance"):
+            cls.instance = super(CCP, cls).__new__(cls, *args, **kwargs)
+            cls.instance.rest = REST(serverIP, serverPort, softVersion)
+            cls.instance.rest.setAccount(accountSid, accountToken)
+            cls.instance.rest.setAppId(appId)
+        return cls.instance
+
+    def send_template_sms(self, to, datas, temp_id):
+        """发送模板短信"""
+        # @param to 手机号码
+        # @param datas 内容数据 格式为数组 例如:{'12','34'},如不需替换请填 ''
+        # @param temp_id 模板Id
+        result = self.rest.sendTemplateSMS(to, datas, temp_id)
+        print(result)
+        # 如果云通讯发送短信成功,返回的字典数据result中statuCode字段的值为"000000"
+        if result.get("statusCode") == "000000":
+            # 返回0 表示发送短信成功
+            return 0
+        else:
+            # 返回-1 表示发送失败
+            return -1
+
+
+if __name__ == '__main__':
+    ccp = CCP()
+    ccp.send_template_sms("13590299759", {"123456", "5"}, "1")

+ 75 - 0
app/lib/yuntongxun/sms.py

@@ -0,0 +1,75 @@
+# -*- coding:utf-8 -*-
+
+from .CCPRestSDK import REST
+
+# 说明:主账号,登陆云通讯网站后,可在"控制台-应用"中看到开发者主账号ACCOUNT SID
+_accountSid = '8aaf0708568d4143015697b0f4960888'
+
+# 说明:主账号Token,登陆云通讯网站后,可在控制台-应用中看到开发者主账号AUTH TOKEN
+_accountToken = '57c6c3ef3cef47e680519a734f6812f8'
+
+# 请使用管理控制台首页的APPID或自己创建应用的APPID
+_appId = '8aaf0708568d4143015697b0f56e088f'
+
+# 说明:请求地址,生产环境配置成app.cloopen.com
+_serverIP = 'sandboxapp.cloopen.com'
+
+# 说明:请求端口 ,生产环境为8883
+_serverPort = "8883"
+
+# 说明:REST API版本号保持不变
+_softVersion = '2013-12-26'
+
+# 云通讯官方提供的发送短信代码实例
+# # 发送模板短信
+# # @param to 手机号码
+# # @param datas 内容数据 格式为数组 例如:{'12','34'},如不需替换请填 ''
+# # @param $tempId 模板Id
+#
+# def sendTemplateSMS(to, datas, tempId):
+#     # 初始化REST SDK
+#     rest = REST(serverIP, serverPort, softVersion)
+#     rest.setAccount(accountSid, accountToken)
+#     rest.setAppId(appId)
+#
+#     result = rest.sendTemplateSMS(to, datas, tempId)
+#     for k, v in result.iteritems():
+#
+#         if k == 'templateSMS':
+#             for k, s in v.iteritems():
+#                 print '%s:%s' % (k, s)
+#         else:
+#             print '%s:%s' % (k, v)
+
+
+class CCP(object):
+    """发送短信的辅助类"""
+
+    def __new__(cls, *args, **kwargs):
+        # 判断是否存在类属性_instance,_instance是类CCP的唯一对象,即单例
+        if not hasattr(CCP, "_instance"):
+            cls._instance = super(CCP, cls).__new__(cls, *args, **kwargs)
+            cls._instance.rest = REST(_serverIP, _serverPort, _softVersion)
+            cls._instance.rest.setAccount(_accountSid, _accountToken)
+            cls._instance.rest.setAppId(_appId)
+        return cls._instance
+
+    def send_template_sms(self, to, datas, temp_id):
+        """发送模板短信"""
+        # @param to 手机号码
+        # @param datas 内容数据 格式为数组 例如:{'12','34'},如不需替换请填 ''
+        # @param temp_id 模板Id
+        result = self.rest.sendTemplateSMS(to, datas, temp_id)
+        # 如果云通讯发送短信成功,返回的字典数据result中statuCode字段的值为"000000"
+        if result.get("statusCode") == "000000":
+            # 返回0 表示发送短信成功
+            return 0
+        else:
+            # 返回-1 表示发送失败
+            return -1
+
+
+if __name__ == '__main__':
+    ccp = CCP()
+    # 注意: 测试的短信模板编号为1
+    ccp.send_template_sms('18516952650', ['1234', 5], 1)

+ 170 - 0
app/lib/yuntongxun/xmltojson.py

@@ -0,0 +1,170 @@
+# -*- coding: utf-8 -*-
+# python xml.etree.ElementTree
+
+import os
+import xml.etree.ElementTree as ET
+from xml.dom import minidom
+
+
+class xmltojson:
+    # global var
+    # show log
+    SHOW_LOG = True
+    # XML file
+    XML_PATH = None
+    a = {}
+    m = []
+
+    def get_root(self, path):
+        '''parse the XML file,and get the tree of the XML file
+        finally,return the root element of the tree.
+        if the XML file dose not exist,then print the information'''
+        # if os.path.exists(path):
+        # if SHOW_LOG:
+        # print('start to parse the file : [{}]'.format(path))
+        tree = ET.fromstring(path)
+        return tree
+        # else:
+        # print('the path [{}] dose not exist!'.format(path))
+
+    def get_element_tag(self, element):
+        '''return the element tag if the element is not None.'''
+        if element is not None:
+
+            return element.tag
+        else:
+            print('the element is None!')
+
+    def get_element_attrib(self, element):
+        '''return the element attrib if the element is not None.'''
+        if element is not None:
+
+            return element.attrib
+        else:
+            print('the element is None!')
+
+    def get_element_text(self, element):
+        '''return the text of the element.'''
+        if element is not None:
+            return element.text
+        else:
+            print('the element is None!')
+
+    def get_element_children(self, element):
+        '''return the element children if the element is not None.'''
+        if element is not None:
+
+            return [c for c in element]
+        else:
+            print('the element is None!')
+
+    def get_elements_tag(self, elements):
+        '''return the list of tags of element's tag'''
+        if elements is not None:
+            tags = []
+            for e in elements:
+                tags.append(e.tag)
+            return tags
+        else:
+            print('the elements is None!')
+
+    def get_elements_attrib(self, elements):
+        '''return the list of attribs of element's attrib'''
+        if elements is not None:
+            attribs = []
+            for a in elements:
+                attribs.append(a.attrib)
+            return attribs
+        else:
+            print('the elements is None!')
+
+    def get_elements_text(self, elements):
+        '''return the dict of element'''
+        if elements is not None:
+            text = []
+            for t in elements:
+                text.append(t.text)
+            return dict(zip(self.get_elements_tag(elements), text))
+        else:
+            print('the elements is None!')
+
+    def main(self, xml):
+        # root
+        root = self.get_root(xml)
+
+        # children
+        children = self.get_element_children(root)
+
+        children_tags = self.get_elements_tag(children)
+
+        children_attribs = self.get_elements_attrib(children)
+
+        i = 0
+
+        # 获取二级元素的每一个子节点的名称和值
+        for c in children:
+            p = 0
+            c_children = self.get_element_children(c)
+            dict_text = self.get_elements_text(c_children)
+            if dict_text:
+                # print (children_tags[i])
+                if children_tags[i] == 'TemplateSMS':
+                    self.a['templateSMS'] = dict_text
+                else:
+                    if children_tags[i] == 'SubAccount':
+                        k = 0
+
+                        for x in children:
+                            if children_tags[k] == 'totalCount':
+                                self.m.append(dict_text)
+                                self.a['SubAccount'] = self.m
+                                p = 1
+                            k = k + 1
+                        if p == 0:
+                            self.a[children_tags[i]] = dict_text
+                    else:
+                        self.a[children_tags[i]] = dict_text
+
+
+            else:
+                self.a[children_tags[i]] = c.text
+            i = i + 1
+        return self.a
+
+    def main2(self, xml):
+        # root
+        root = self.get_root(xml)
+
+        # children
+        children = self.get_element_children(root)
+
+        children_tags = self.get_elements_tag(children)
+
+        children_attribs = self.get_elements_attrib(children)
+
+        i = 0
+
+        # 获取二级元素的每一个子节点的名称和值
+        for c in children:
+            p = 0
+            c_children = self.get_element_children(c)
+            dict_text = self.get_elements_text(c_children)
+            if dict_text:
+                if children_tags[i] == 'TemplateSMS':
+                    k = 0
+
+                    for x in children:
+                        if children_tags[k] == 'totalCount':
+                            self.m.append(dict_text)
+                            self.a['TemplateSMS'] = self.m
+                            p = 1
+                        k = k + 1
+                    if p == 0:
+                        self.a[children_tags[i]] = dict_text
+                else:
+                    self.a[children_tags[i]] = dict_text
+
+            else:
+                self.a[children_tags[i]] = c.text
+            i = i + 1
+        return self.a

+ 198 - 0
app/models.py

@@ -0,0 +1,198 @@
+from datetime import datetime
+from werkzeug.security import generate_password_hash, check_password_hash
+
+from app import constants
+from app import db
+
+
+class BaseModel(object):
+    """模型基类,为每个模型补充创建时间与更新时间"""
+    create_time = db.Column(db.DateTime, default=datetime.now)  # 记录的创建时间
+    update_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)  # 记录的更新时间
+
+
+# 用户收藏表,建立用户与其收藏新闻多对多的关系
+tb_user_collection = db.Table(
+    "info_user_collection",
+    db.Column("user_id", db.Integer, db.ForeignKey("info_user.id"), primary_key=True),  # 新闻编号
+    db.Column("news_id", db.Integer, db.ForeignKey("info_news.id"), primary_key=True),  # 分类编号
+    db.Column("create_time", db.DateTime, default=datetime.now)  # 收藏创建时间
+)
+
+tb_user_follows = db.Table(
+    "info_user_fans",
+    db.Column('follower_id', db.Integer, db.ForeignKey('info_user.id'), primary_key=True),  # 粉丝id
+    db.Column('followed_id', db.Integer, db.ForeignKey('info_user.id'), primary_key=True)  # 被关注人的id
+)
+
+
+class User(BaseModel, db.Model):
+    """用户"""
+    __tablename__ = "info_user"
+
+    id = db.Column(db.Integer, primary_key=True)  # 用户编号
+    nick_name = db.Column(db.String(32), unique=True, nullable=False)  # 用户昵称
+    password_hash = db.Column(db.String(128), nullable=False)  # 加密的密码
+    mobile = db.Column(db.String(11), unique=True, nullable=False)  # 手机号
+    avatar_url = db.Column(db.String(256))  # 用户头像路径
+    last_login = db.Column(db.DateTime, default=datetime.now)  # 最后一次登录时间
+    is_admin = db.Column(db.Boolean, default=False)
+    signature = db.Column(db.String(512))  # 用户签名
+    gender = db.Column(  # 订单的状态
+        db.Enum(
+            "MAN",  # 男
+            "WOMAN"  # 女
+        ),
+        default="MAN")
+
+    # 当前用户收藏的所有新闻
+    collection_news = db.relationship("News", secondary=tb_user_collection, lazy="dynamic")  # 用户收藏的新闻
+    # 用户所有的粉丝,添加了反向引用followed,代表用户都关注了哪些人
+    followers = db.relationship('User',
+                                secondary=tb_user_follows,
+                                primaryjoin=id == tb_user_follows.c.followed_id,
+                                secondaryjoin=id == tb_user_follows.c.follower_id,
+                                backref=db.backref('followed', lazy='dynamic'),
+                                lazy='dynamic')
+
+    # 当前用户所发布的新闻
+    news_list = db.relationship('News', backref='user', lazy='dynamic')
+
+    @property
+    def password(self):
+        raise AttributeError("当前属性不可读")
+
+    @password.setter
+    def password(self, value):
+        self.password_hash = generate_password_hash(value)
+
+    def check_passowrd(self, password):
+        return check_password_hash(self.password_hash, password)
+
+    def to_dict(self):
+        resp_dict = {
+            "id": self.id,
+            "nick_name": self.nick_name,
+            "avatar_url": constants.QINIU_DOMIN_PREFIX + self.avatar_url if self.avatar_url else "",
+            "mobile": self.mobile,
+            "gender": self.gender if self.gender else "MAN",
+            "signature": self.signature if self.signature else "",
+            "followers_count": self.followers.count(),
+            "news_count": self.news_list.count()
+        }
+        return resp_dict
+
+    def to_admin_dict(self):
+        resp_dict = {
+            "id": self.id,
+            "nick_name": self.nick_name,
+            "mobile": self.mobile,
+            "register": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
+            "last_login": self.last_login.strftime("%Y-%m-%d %H:%M:%S"),
+        }
+        return resp_dict
+
+
+class News(BaseModel, db.Model):
+    """新闻"""
+    __tablename__ = "info_news"
+
+    id = db.Column(db.Integer, primary_key=True)  # 新闻编号
+    title = db.Column(db.String(256), nullable=False)  # 新闻标题
+    source = db.Column(db.String(64), nullable=False)  # 新闻来源
+    digest = db.Column(db.String(512), nullable=False)  # 新闻摘要
+    content = db.Column(db.Text, nullable=False)  # 新闻内容
+    clicks = db.Column(db.Integer, default=0)  # 浏览量
+    index_image_url = db.Column(db.String(256))  # 新闻列表图片路径
+    category_id = db.Column(db.Integer, db.ForeignKey("info_category.id"))
+    user_id = db.Column(db.Integer, db.ForeignKey("info_user.id"))  # 当前新闻的作者id
+    status = db.Column(db.Integer, default=0)  # 当前新闻状态 如果为0代表审核通过,1代表审核中,-1代表审核不通过
+    reason = db.Column(db.String(256))  # 未通过原因,status = -1 的时候使用
+    # 当前新闻的所有评论
+    comments = db.relationship("Comment", lazy="dynamic")
+
+    def to_review_dict(self):
+        resp_dict = {
+            "id": self.id,
+            "title": self.title,
+            "create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
+            "status": self.status,
+            "reason": self.reason if self.reason else ""
+        }
+        return resp_dict
+
+    def to_basic_dict(self):
+        resp_dict = {
+            "id": self.id,
+            "title": self.title,
+            "source": self.source,
+            "digest": self.digest,
+            "create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
+            "index_image_url": self.index_image_url,
+            "clicks": self.clicks,
+        }
+        return resp_dict
+
+    def to_dict(self):
+        resp_dict = {
+            "id": self.id,
+            "title": self.title,
+            "source": self.source,
+            "digest": self.digest,
+            "create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
+            "content": self.content,
+            "comments_count": self.comments.count(),
+            "clicks": self.clicks,
+            "category": self.category.to_dict(),
+            "index_image_url": self.index_image_url,
+            "author": self.user.to_dict() if self.user else None
+        }
+        return resp_dict
+
+
+class Comment(BaseModel, db.Model):
+    """评论"""
+    __tablename__ = "info_comment"
+
+    id = db.Column(db.Integer, primary_key=True)  # 评论编号
+    user_id = db.Column(db.Integer, db.ForeignKey("info_user.id"), nullable=False)  # 用户id
+    news_id = db.Column(db.Integer, db.ForeignKey("info_news.id"), nullable=False)  # 新闻id
+    content = db.Column(db.Text, nullable=False)  # 评论内容
+    parent_id = db.Column(db.Integer, db.ForeignKey("info_comment.id"))  # 父评论id
+    parent = db.relationship("Comment", remote_side=[id])  # 自关联
+    like_count = db.Column(db.Integer, default=0)  # 点赞条数
+
+    def to_dict(self):
+        resp_dict = {
+            "id": self.id,
+            "create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S"),
+            "content": self.content,
+            "parent": self.parent.to_dict() if self.parent else None,
+            "user": User.query.get(self.user_id).to_dict(),
+            "news_id": self.news_id,
+            "like_count": self.like_count
+        }
+        return resp_dict
+
+
+class CommentLike(BaseModel, db.Model):
+    """评论点赞"""
+    __tablename__ = "info_comment_like"
+    comment_id = db.Column("comment_id", db.Integer, db.ForeignKey("info_comment.id"), primary_key=True)  # 评论编号
+    user_id = db.Column("user_id", db.Integer, db.ForeignKey("info_user.id"), primary_key=True)  # 用户编号
+
+
+class Category(BaseModel, db.Model):
+    """新闻分类"""
+    __tablename__ = "info_category"
+
+    id = db.Column(db.Integer, primary_key=True)  # 分类编号
+    name = db.Column(db.String(64), nullable=False)  # 分类名
+    news_list = db.relationship('News', backref='category', lazy='dynamic')
+
+    def to_dict(self):
+        resp_dict = {
+            "id": self.id,
+            "name": self.name
+        }
+        return resp_dict

+ 0 - 0
app/modules/__init__.py


+ 17 - 0
app/modules/admin/__init__.py

@@ -0,0 +1,17 @@
+from flask import Blueprint, request, url_for, session, redirect
+
+admin_bp = Blueprint("admin", __name__, url_prefix="/admin")
+
+from . import views
+
+
+@admin_bp.before_request
+def before_request():
+    # 判断如果不是登录页面的请求
+    if not request.url_root.endswith(url_for('admin.admin_login')):
+        user_id = session.get('user_id')
+        is_admin = session.get('is_admin', False)
+
+        if not user_id or not is_admin:
+            # 判断当前是否有用户的登录,或者是否是管理员,如果不是,直接重定向到项目主页
+            return redirect('/')

+ 284 - 0
app/modules/admin/views.py

@@ -0,0 +1,284 @@
+import time
+from datetime import datetime, timedelta
+
+from flask import render_template, request, current_app, session, g, redirect, url_for, jsonify
+
+from app import user_login_data, constants, db
+from app.models import User, News
+from app.utils.response_code import RET
+from . import admin_bp
+
+
+@admin_bp.route("/login", methods=["GET", "POST"])
+def admin_login():
+    if request.method == "GET":
+        # 获取session中指定的值
+        user_id = session.get('user_id', None)
+        is_admin = session.get('is_admin', False)
+        # 如果用户id存在,并且是管理员,那么直接跳转管理后台主页
+        if user_id and is_admin:
+            return redirect(url_for('admin.admin_index'))
+        return render_template('admin/login.html')
+
+    # 取到登录的参数
+    username = request.form.get("username")
+    password = request.form.get("password")
+    if not all([username, password]):
+        return render_template('admin/login.html', errmsg='参数不足')
+
+    try:
+        user = User.query.filter(User.mobile == username).first()
+    except Exception as e:
+        current_app.logger.error(e)
+        return render_template('admin/login.html', errmsg="数据查询失败")
+
+    if not user:
+        return render_template('admin/login.html', errmsg="用户不存在")
+
+    if not user.check_passowrd(password):
+        return render_template('admin/login.html', errmsg='密码错误')
+
+    if not user.is_admin:
+        return render_template('admin/login.html', errmsg='用户权限错误')
+
+    session['user_id'] = user.id
+    session['nick_name'] = user.nick_name
+    session['mobile'] = user.mobile
+    if user.is_admin:
+        session['is_admin'] = True
+
+    # 跳转到后台管理主页
+    return redirect(url_for('admin.admin_index'))
+
+
+@admin_bp.route('/')
+@user_login_data
+def admin_index():
+    """
+    站点主页
+    :return:
+    """
+    # 读取登录用户的信息
+    user = g.user
+    # 优化进入主页逻辑:如果管理员进入主页,必须要登录状态,反之就引导到登录界面
+    if not user:
+        return redirect(url_for('admin.admin_login'))
+    # 构造渲染数据
+    data = {
+        'user_info': user.to_dict()
+    }
+    # 渲染主页
+    return render_template('admin/index.html', data=data)
+
+
+@admin_bp.route('/user_count')
+def user_count():
+    # 查询总人数
+    total_count = 0
+    try:
+        total_count = User.query.filter(User.is_admin == False).count()
+    except Exception as e:
+        current_app.logger.error(e)
+
+    # 查询月新增数
+    mon_count = 0
+    try:
+        now = time.localtime()
+        mon_begin = '%d-%02d-01' % (now.tm_year, now.tm_mon)
+        mon_begin_date = datetime.strptime(mon_begin, '%Y-%m-%d')
+        mon_count = User.query.filter(User.is_admin == False, User.create_time >= mon_begin_date).count()
+    except Exception as e:
+        current_app.logger.error(e)
+
+    # 查询日新增数
+    day_count = 0
+    try:
+        day_begin = '%d-%02d-%02d' % (now.tm_year, now.tm_mon, now.tm_mday)
+        day_begin_date = datetime.strptime(day_begin, '%Y-%m-%d')
+        day_count = User.query.filter(User.is_admin == False, User.create_time > day_begin_date).count()
+    except Exception as e:
+        current_app.logger.error(e)
+
+    # 查询图标信息
+    # 获取到当天00:00:00时间
+    now_date = datetime.strptime(datetime.now().strftime('%Y-%m-%d'), '%Y-%m-%d')
+    # 定义空数组,保存数组
+    active_date = []
+    active_count = []
+
+    # 依次添加数据,再反转
+    for i in range(0, 31):
+        begin_date = now_date - timedelta(days=i)
+        end_date = now_date - timedelta(days=(i - 1))
+        active_date.append(begin_date.strftime('%Y-%m-%d'))
+        count = 0
+        try:
+            count = User.query.filter(User.is_admin == False, User.last_login >= begin_date,
+                                      User.last_login < end_date).count()
+        except Exception as e:
+            current_app.logger.error(e)
+
+        active_count.append(count)
+
+    active_date.reverse()
+    active_count.reverse()
+
+    data = {
+        "total_count": total_count,
+        "mon_count": mon_count,
+        "day_count": day_count,
+        "active_date": active_date,
+        "active_count": active_count
+    }
+
+    return render_template('admin/user_count.html', data=data)
+
+
+@admin_bp.route('/user_list')
+def user_list():
+    """获取用户列表"""
+
+    # 获取参数
+    page = request.args.get('p', 1)
+    try:
+        page = int(page)
+    except Exception as e:
+        current_app.logger.error(e)
+        page = 1
+
+    # 设置变量默认值
+    users = []
+    current_page = 1
+    total_page = 1
+
+    # 查询数据
+    try:
+        paginate = User.query.filter(User.is_admin == False) \
+            .order_by(User.last_login.desc()) \
+            .paginate(page, constants.ADMIN_NEWS_PAGE_MAX_COUNT, False)
+        users = paginate.items
+        current_page = paginate.page
+        total_page = paginate.pages
+    except Exception as e:
+        current_app.logger.error(e)
+
+    # 将模型列表转成字典列表
+    users_list = []
+    for user in users:
+        users_list.append(user.to_admin_dict())
+
+    data = {
+        "total_page": total_page,
+        "current_page": current_page,
+        "users": users_list
+    }
+
+    return render_template("admin/user_list.html", data=data)
+
+
+@admin_bp.route('/news_review')
+def news_review():
+    """返回待审核新闻列表"""
+
+    page = request.args.get("p", 1)
+    try:
+        page = int(page)
+    except Exception as e:
+        current_app.logger.error(e)
+        page = 1
+
+    news_list = []
+    current_page = 1
+    total_page = 1
+
+    try:
+        paginate = News.query.filter(News.status != 0) \
+            .order_by(News.create_time.desc()) \
+            .paginate(page, constants.ADMIN_NEWS_PAGE_MAX_COUNT, False)
+
+        news_list = paginate.items
+        current_page = paginate.page
+        total_page = paginate.pages
+    except Exception as e:
+        current_app.logger.error(e)
+
+    news_dict_list = []
+    for news in news_list:
+        news_dict_list.append(news.to_review_dict())
+
+    data = {
+        "total_page": total_page,
+        "current_page": current_page,
+        "news_list": news_dict_list
+    }
+
+    return render_template('admin/news_review.html', data=data)
+
+
+@admin_bp.route('/news_review_detail', methods=["GET", "POST"])
+def news_review_detail():
+    """新闻审核"""
+    if request.method == "GET":
+        # 获取新闻id
+        news_id = request.args.get("news_id")
+        if not news_id:
+            return render_template("admin/news_review_detail.html", data={"errmsg", "未查询到该新闻"})
+
+        # 通过id查询新闻
+        try:
+            news = News.query.get(news_id)
+        except Exception as e:
+            current_app.logger.error(e)
+
+        if not news:
+            return render_template("admin/news_review_detail.html", data={"errmsg", "未查询到该新闻"})
+
+        # 返回数据
+        data = {
+            "news": news.to_dict()
+        }
+        return render_template("admin/news_review_detail.html", data=data)
+
+    # 执行审核操作
+
+    # 1.获取参数
+    news_id = request.json.get("news_id")
+    action = request.json.get("action")
+
+    # 2.判断参数
+    if not all([news_id, action]):
+        return jsonify(errno=RET.PARAMERR, errmsg="参数错误")
+
+    if action not in ("accept", "reject"):
+        return jsonify(errno=RET.PARAMERR, errmsg="参数错误")
+
+    news = None
+    try:
+        # 3.查询新闻
+        news = News.query.get(news_id)
+    except Exception as e:
+        current_app.logger.error(e)
+
+    if not news:
+        return jsonify(errno=RET.NODATA, errmsg="未查询到数据")
+
+    # 4.根据不同的状态设置不同的值
+    if action == "accept":
+        news.status = 0
+    else:
+        # 拒绝通过,需要获取原因
+        reason = request.json.get("reson")
+        if not reason:
+            return jsonify(errno=RET.PARAMERR, errmsg="参数错误")
+        news.reason = reason
+        news.status = -1
+
+    # 保存数据库
+    try:
+        db.session.commit()
+    except Exception as e:
+        current_app.logger.error(e)
+        db.session.rollback()
+        return jsonify(errno=RET.DBERR, errmsg="数据保存失败")
+
+    return jsonify(errno=RET.OK, errmsg="操作成功")

+ 5 - 0
app/modules/index/__init__.py

@@ -0,0 +1,5 @@
+from flask import Blueprint
+
+index_bp = Blueprint("index", __name__)
+
+from . import views

+ 106 - 0
app/modules/index/views.py

@@ -0,0 +1,106 @@
+from app import constants
+from app.models import User, News, Category
+from app.utils.response_code import RET
+from . import index_bp
+from flask import render_template, current_app, session, request, jsonify
+
+
+@index_bp.route('/')
+def index():
+    # 获取到当前登录用户的id
+    user_id = session.get("user_id")
+    # 通过id获取用户信息
+    user = None  # type = User
+    if user_id:
+        try:
+            user = User.query.get(user_id)
+        except Exception as e:
+            current_app.logger.error(e)
+
+    # 获取点击排行数据
+    new_list = None
+    try:
+        new_list = News.query.order_by(News.clicks.desc()).limit(constants.CLICK_RANK_MAX_NEWS)
+    except Exception as e:
+        current_app.logger.error(e)
+
+    click_news_list = []
+    for news in new_list if new_list else []:
+        click_news_list.append(news.to_basic_dict())
+
+    # 获取新闻分类数据
+    categories = Category.query.all()
+    # 定义列表保存分类数据
+    categories_dicts = []
+    for category in categories:
+        # 拼接内容
+        # print(category)
+        categories_dicts.append(category.to_dict())
+
+    data = {
+        'user_info': user.to_dict() if user else None,
+        'click_news_list': click_news_list,
+        'categories': categories_dicts
+    }
+
+    return render_template("news/index.html", data=data)
+
+
+@index_bp.route('/favicon.ico')
+def favicon():
+    return current_app.send_static_file("news/favicon.ico")
+
+
+@index_bp.route('/newslist')
+def get_news_list():
+    """
+    获取指定分类的新闻列表
+    1.获取参数
+    2.校验参数
+    3.查询数据
+    4.返回数据
+    :return:
+    """
+    # 1.获取参数
+    args_dict = request.args
+    page = args_dict.get("page", "1")
+    per_page = args_dict.get("per_page", constants.HOME_PAGE_MAX_NEWS)
+    category_id = args_dict.get("cid", "1")
+
+    # 2.校验参数
+    try:
+        page = int(page)
+        per_page = int(per_page)
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(errno=RET.PARAMERR, errmsg="参数错误")
+
+    # 3.查询数据并分页
+    filters = []
+    # 如果不分类id不为1,那么添加分类id的过滤
+    if category_id != "1":
+        filters.append(News.category_id == category_id)
+
+    try:
+        paginate = News.query.filter(*filters).order_by(News.create_time.desc()).paginate(page, per_page, False)
+        # 获取查询出来的数据
+        items = paginate.items
+        # 获取到总页数
+        total_page = paginate.pages
+        # 获取到当前页数
+        current_page = paginate.page
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(errno=RET.DBERR, errmsg="数据查询失败")
+
+    news_lst = []
+    for news in items:
+        news_lst.append(news.to_dict())
+
+    data = {
+        "news_list": news_lst,
+        "current_page": current_page,
+        "total_page": total_page
+    }
+
+    return jsonify(errno=RET.OK, errmsg="查询新闻列表数据成功", data=data)

+ 5 - 0
app/modules/news/__init__.py

@@ -0,0 +1,5 @@
+from flask import Blueprint
+
+news_bp = Blueprint("news", __name__, url_prefix='/news')
+
+from . import views

+ 226 - 0
app/modules/news/views.py

@@ -0,0 +1,226 @@
+from app import constants, db
+from app.models import News, Comment, CommentLike
+from app.utils.common import user_login_data
+from app.utils.response_code import RET
+from . import news_bp
+from flask import render_template, current_app, abort, g, request, jsonify
+
+
+@news_bp.route("/<int:news_id>")
+@user_login_data
+def news_detail(news_id):
+    try:
+        cur_news = News.query.get(news_id)
+    except Exception as e:
+        current_app.logger.error(e)
+        abort(404)
+
+    if not cur_news:
+        # 返回数据未找到的页面
+        abort(404)
+
+    cur_news.clicks += 1
+
+    # 获取点击排行数据
+    new_list = None
+    try:
+        news_list = News.query.order_by(News.clicks.desc()).limit(constants.CLICK_RANK_MAX_NEWS)
+    except Exception as e:
+        current_app.logger.error(e)
+
+    click_news_list = []
+    for news in news_list if news_list else []:
+        click_news_list.append(news.to_dict())
+
+    # 判断是否收藏该新闻,默认值为false
+    is_collected = False
+    # 判断用户是否收藏过该新闻
+    if g.user:
+        if cur_news in g.user.collection_news:
+            is_collected = True
+
+    # 获取当前新闻评论
+    comments = []
+    try:
+        comments = (Comment.query.filter(Comment.news_id == news_id)) \
+            .order_by(Comment.create_time.desc()).all()
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(errno=RET.DBERR, errmsg="评论查询失败")
+
+    # 获取评论点赞
+    comment_like_ids = []
+    if g.user:
+        # 如果当前用户已登录
+        try:
+            comment_ids = [comment.id for comment in comments]
+            if len(comment_ids) > 0:
+                # 取到当前用户在当前新闻的所有评论点赞的记录
+                comment_likes = CommentLike.query.filter(CommentLike.comment_id.in_(comment_ids),
+                                                         CommentLike.user_id == g.user.id).all()
+                # 取出记录中所有的评论id
+                comment_like_ids = [comment_like.comment_id for comment_like in comment_likes]
+        except Exception as e:
+            current_app.logger.error(e)
+
+    comment_list = []
+    for item in comments if comments else []:
+        comment_dict = item.to_dict()
+        comment_dict["is_like"] = False
+        # 判断用户是否点赞该评论
+        if g.user and item.id in comment_like_ids:
+            comment_dict["is_like"] = True
+        comment_list.append(comment_dict)
+
+    data = {
+        "news": cur_news.to_dict(),
+        "user_info": g.user.to_dict() if g.user else None,
+        "click_news_list": click_news_list,
+        "is_collected": is_collected,
+        "comments": comment_list
+    }
+    return render_template('news/detail.html', data=data)
+
+
+@news_bp.route("/news_collect", methods=['POST'])
+@user_login_data
+def news_collect():
+    """新闻收藏"""
+
+    user = g.user
+    json_data = request.json
+    print(type(json_data))
+    news_id = json_data.get("news_id")
+    action = json_data.get("action")
+
+    if not user:
+        return jsonify(errno=RET.SESSIONERR, errmsg="用户未登录")
+
+    if not news_id:
+        return jsonify(errno=RET.PARAMERR, errmsg="参数错误")
+
+    if action not in ("collect", "cancel_collect"):
+        return jsonify(errno=RET.PARAMERR, errmsg="参数错误")
+
+    try:
+        news = News.query.get(news_id)
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(error=RET.DBERR, errmsg="查询数据失败")
+
+    if not news:
+        return jsonify(errno=RET.NODATA, errmsg="新闻数据不存在")
+
+    if action == "collect":
+        user.collection_news.append(news)
+    else:
+        user.collection_news.remove(news)
+
+    try:
+        db.session.commit()
+    except Exception as e:
+        current_app.logger.error(e)
+        db.session.rollback()
+        return jsonify(errno=RET.DBERR, errmsg="数据保存失败")
+
+    return jsonify(errno=RET.OK, errmsg="操作成功")
+
+
+@news_bp.route("/news_comment", methods=['POST'])
+@user_login_data
+def add_news_comment():
+    """添加评论"""
+    user = g.user
+    if not user:
+        return jsonify(errno=RET.SESSIONERR, errmsg="用户未登陆")
+    # 获取参数
+    data_dict = request.json
+    news_id = data_dict.get("news_id")
+    comment_str = data_dict.get("comment")
+    parent_id = data_dict.get("parent_id")
+
+    if not all([news_id, comment_str]):
+        jsonify(errno=RET.PARAMERR, errmsg="参数错误")
+
+    try:
+        news = News.query.get(news_id)
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(errno=RET.DBERR, errmsg="数据查询失败")
+
+    if not news:
+        return jsonify(errno=RET.NODATA, errmsg="该新闻不存在")
+
+    # 初始化模型,保存数据
+    comment = Comment()
+    comment.user_id = user.id
+    comment.news_id = news_id
+    comment.content = comment_str
+
+    if parent_id:
+        comment.parent_id = parent_id
+
+    try:
+        db.session.add(comment)
+        db.session.commit()
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(errno=RET.DBERR, errmsg="保存评论数据失败")
+
+    # 返回响应
+    return jsonify(errno=RET.OK, errmsg="评论成功", data=comment.to_dict())
+
+
+@news_bp.route("/comment_like", methods=['POST'])
+@user_login_data
+def set_comment_like():
+    """评论点赞"""
+    if not g.user:
+        return jsonify(errno=RET.SESSIONERR, errmsg="用户未登陆")
+
+    # 获取参数
+    comment_id = request.json.get("comment_id")
+    news_id = request.json.get("news_id")
+    action = request.json.get("action")
+
+    # 判断参数
+    if not all([comment_id, news_id, action]):
+        return jsonify(errno=RET.PARAMERR, errmsg="参数有误")
+
+    # 查询评论数据
+    try:
+        comment = Comment.query.get(comment_id)
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(errno=RET.DBERR, errmsg="数据查询失败")
+
+    if not comment:
+        return jsonify(errno=RET.NODATA, errmsg="评论数据不存在")
+
+    if action == "add":
+        comment_like = CommentLike.query.filter(CommentLike.comment_id == comment_id,
+                                                CommentLike.user_id == g.user.id).first()
+        if not comment_like:
+            comment_like = CommentLike()
+            comment_like.comment_id = comment_id
+            comment_like.user_id = g.user.id
+            db.session.add(comment_like)
+            # 增加点赞条数
+            comment.like_count += 1
+    else:
+        # 删除点赞数据
+        comment_like = CommentLike.query.filter(CommentLike.comment_id == comment_id,
+                                                CommentLike.user_id == g.user.id).first()
+        if comment_like:
+            db.session.delete(comment_like)
+            # 减少点赞条数
+            comment.like_count -= 1
+
+    try:
+        db.session.commit()
+    except Exception as e:
+        current_app.logger.error(e)
+        db.session.rollback()
+        return jsonify(errno=RET.DBERR, errmsg="数据更新失败")
+
+    return jsonify(errno=RET.OK, errmsg="操作成功")

+ 6 - 0
app/modules/passport/__init__.py

@@ -0,0 +1,6 @@
+from flask import Blueprint
+
+# 创建蓝图,并设置蓝图前缀
+passport_bp = Blueprint("passport", __name__,url_prefix='/passport')
+
+from . import views

+ 254 - 0
app/modules/passport/views.py

@@ -0,0 +1,254 @@
+import random
+import re
+
+from datetime import datetime
+from flask import request, session
+
+from app.lib.yuntongxun.cpp import CCP
+from app.models import User
+from app.utils.response_code import RET
+from . import passport_bp
+from app.utils.captcha.captcha import captcha
+from app import redis_store, constants, db
+from flask import current_app, make_response, jsonify
+
+
+@passport_bp.route('/image_code')
+def get_image_code():
+    """
+    获取图片验证码
+    :return:
+    """
+    # 1.获取当前的图片编号id
+    code_id = request.args.get('code_id')
+    # 2.生成验证码
+    name, text, image, = captcha.generate_captcha()
+
+    try:
+        # 保存当前生成的图片验证码内容
+        redis_store.setex('ImageCode_' + code_id, constants.IMAGE_CODE_REDIS_EXPIRES, text)
+    except Exception as e:
+        current_app.logger.error(e)
+        return make_response(jsonify(errno=RET.DATAERR, errmsg='保存图片验证码失败'))
+
+    # 返回相应内容
+    resp = make_response(image)
+    # 设置内容类型
+    resp.headers['Content-Type'] = 'image/jpg'
+    return resp
+
+
+@passport_bp.route('/smscode', methods=['POST'])
+def send_sms():
+    """
+        1. 接收参数并判断是否有值
+        2. 校验手机号是正确
+        3. 通过传入的图片编码去redis中查询真实的图片验证码内容
+        4. 进行验证码内容的比对
+        5. 生成发送短信的内容并发送短信
+        6. redis中保存短信验证码内容
+        7. 返回发送成功的响应
+        :return:
+        """
+    # 1.获取参数,并判断参数是否有值
+    param_dict = request.json
+    mobile = param_dict.get("mobile")
+    image_code = param_dict.get('image_code')
+    image_code_id = param_dict.get("image_code_id")
+
+    # 判断参数是否齐全
+    if not all([mobile, image_code, image_code_id]):
+        return jsonify(errno=RET.PARAMERR, errmsg="参数不全")
+
+    # 2.校验手机号是否正确
+    print(mobile)
+    # 2. 校验手机号是正确
+    if not re.match("^1[3578][0-9]{9}$", mobile):
+        # 提示手机号不正确
+        return jsonify(errno=RET.DATAERR, errmsg="手机号不正确")
+
+    # 3.通过传入的图片编码去redis中查询真实的图片验证码内容
+    try:
+        real_image_code = redis_store.get("ImageCode_" + image_code_id)
+    except Exception as e:
+        current_app.looger.error(e)
+        # 获取图片验证码失败
+        return jsonify(errno=RET.DBERR, errmsg="获取图片验证码失败")
+
+    # 3.1判断验证码是否存在,已过期
+    if not real_image_code:
+        # 验证码已过期
+        return jsonify(errno=RET.NODATA, errmsg="验证码已过期")
+
+    # 4.进行验证码内容的比对
+    if image_code.lower() != real_image_code.lower():
+        # 验证码输入错误
+        return jsonify(errno=RET.DATAERR, errmsg="验证码输入错误")
+
+    # 4.1校验该手机是否已经注册
+    try:
+        user = User.query.filter(User.mobile == mobile).first()
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(errno=RET.DBERR, errmsg="数据库查询失败")
+    if user:
+        # 该手机已被注册
+        return jsonify(errno=RET.DATAEXIST, errmsg="该手机已被注册")
+
+    # 5. 生成发送短信的内容并发送短信
+    result = random.randint(0, 999999)
+    sms_code = "%06d" % result
+    current_app.logger.debug("短信验证码的内容:%s" % sms_code)
+    ccp = CCP()
+    result = ccp.send_template_sms(mobile,
+                                   {sms_code, "5"}, "1")
+    if result != 0:
+        # 发送短信失败
+        return jsonify(errno=RET.THIRDERR, errmsg="发送短信失败")
+
+    # 6.redis中保存短信验证码内容
+    try:
+        redis_store.set("SMS_" + mobile, sms_code, constants.SMS_CODE_REDIS_EXPIRES)
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(errno=RET.DBERR, errmsg="保存短信验证码失败")
+
+    # 7.返回发送成功的响应
+    return jsonify(errno=RET.OK, errmsg="发送成功")
+
+
+@passport_bp.route('/register', methods=['POST'])
+def register():
+    """
+    1.获取参数和判断是否有值
+    2.从redis中获取指定手机号对应的短信验证码的
+    3.校验验证码
+    4.初始化user模型, 并设置数据并添加到数据库
+    5.保存当前用户的状态
+    6.返回注册的结果
+    :return:
+    """
+
+    # 1.获取参数和判断是否有值
+    param_data = request.json
+    mobile = param_data.get("mobile")
+    sms_code = param_data.get("smscode")
+    password = param_data.get("password")
+
+    if not all([mobile, sms_code, password]):
+        # 参数不全
+        return jsonify(errno=RET.PARAMERR, errmsg="参数不全")
+
+    # 2.从redis中获取指定手机号对应的短信验证码的
+    try:
+        real_sms_code = redis_store.get("SMS_" + mobile)
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(errno=RET.DBERR, errmsg="获取本地校验码失败")
+
+    if not real_sms_code:
+        # 短信验证码过期
+        return jsonify(errno=RET.NODATA, errmsg="短信验证码过期")
+
+    # 3.校验验证码
+    if sms_code != real_sms_code:
+        return jsonify(errno=RET.DATAERR, errmsg="短信验证码错误")
+
+    # 删除短信验证码
+    try:
+        redis_store.delete("SMS_" + mobile)
+    except Exception as e:
+        current_app.logger.error(e)
+
+    # 4.初始化user原型,并设置数据并添加到数据库
+    user = User()
+    user.nick_name = mobile
+    user.mobile = mobile
+    # 对密码进行处理
+    user.password = password
+
+    try:
+        db.session.add(user)
+        db.session.commit()
+    except Exception as e:
+        db.session.rollback()
+        current_app.logger.error(e)
+        # 数据保存错误
+        return jsonify(errno=RET.DATAERR, errmsg="数据保存错误")
+
+    # 5.保存用户登录状态
+    session["user_id"] = user.id
+    session["nick_name"] = user.nick_name
+    session["mobile"] = user.mobile
+    if user.is_admin:
+        session["is_admin"] = True
+
+    # 6.返回注册结果
+    return jsonify(errno=RET.OK, errmsg="OK")
+
+
+@passport_bp.route('/login', methods=['POST'])
+def login():
+    """
+    1.获取参数和判断是否有值
+    2.从数据库查询出指定的用户
+    3.校验密码
+    4.保存用户登录状态
+    5.返回结果
+    :return:
+    """
+
+    # 1.获取参数和判断是否有值
+    param_data = request.json
+    mobile = param_data.get('mobile')
+    password = param_data.get("password")
+
+    if not all([mobile, password]):
+        # 参数不全
+        return jsonify(errno=RET.PARAMERR, errmsg="参数不全")
+
+    # 2.从数据库查询出指定的用户
+    try:
+        user = User.query.filter(User.mobile == mobile).first()
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(errno=RET.DBERR, errmsg="查询数据错误")
+
+    if not user:
+        return jsonify(errno=RET.USERERR, errmsg="用户不存在")
+
+    # 3.校验密码
+    if not user.check_passowrd(password):
+        return jsonify(errno=RET.PWDERR, errmsg="密码错误")
+
+    # 4.保存用户登录状态
+    session["user_id"] = user.id
+    session["nick_name"] = user.nick_name
+    session["mobile"] = user.mobile
+    if user.is_admin:
+        session["is_admin"] = True
+    # 记录用户最后一次登录时间
+    user.last_login = datetime.now()
+
+    try:
+        db.session.commit()
+    except Exception as e:
+        current_app.logger.error(e)
+
+    # 5.登录成功
+    return jsonify(errno=RET.OK, errmsg="OK")
+
+
+@passport_bp.route('/logout', methods=["POST"])
+def logout():
+    """
+    清除session中的对应登录之后保存的信息
+    :return:
+    """
+    session.pop('user_id', None)
+    session.pop('nick_name', None)
+    session.pop('mobile', None)
+    session.pop('is_admin', None)
+
+    # 返回结果
+    return jsonify(errno=RET.OK, errmsg="OK")

+ 5 - 0
app/modules/profile/__init__.py

@@ -0,0 +1,5 @@
+from flask import Blueprint
+
+profile_bp = Blueprint("profile", __name__, url_prefix="/profile")
+
+from . import views

+ 307 - 0
app/modules/profile/views.py

@@ -0,0 +1,307 @@
+from flask import g, redirect, render_template, request, jsonify, current_app, session
+from app import user_login_data, db, constants
+from app.models import Category, News
+from app.utils.pic_storage import pic_storage
+from app.utils.response_code import RET
+from . import profile_bp
+
+
+@profile_bp.route('/info')
+@user_login_data
+def get_user_info():
+    """
+    获取用户信息
+    1.获取到当前登录的用户模型
+    2.返回模型中的指定内容
+    :return:
+    """
+
+    user = g.user
+    if not user:
+        # 用户未登录,重定向到主页
+        return redirect('/')
+
+    data = {
+        "user_info": user.to_dict()
+    }
+
+    return render_template("profile/user.html", data=data)
+
+
+@profile_bp.route("/base_info", methods=["GET", "POST"])
+@user_login_data
+def get_base_info():
+    """
+    用户基本信息
+    1.获取用户登录信息
+    2.获取到传入参数
+    3.更新并保存数据
+    4.返回结果
+    :return:
+    """
+    # 1.获取当前登录用户的信息
+    user = g.user
+    # 判断是否为get方法,若是则直接返回用户信息
+    if request.method == "GET":
+        return render_template("profile/user_base_info.html", data={"user_info": user.to_dict()})
+
+    # 2.获取到传入参数
+    data_dict = request.json
+    nick_name = data_dict.get("nick_name")
+    gender = data_dict.get("gender")
+    signature = data_dict.get("signature")
+    if not all([nick_name, gender, signature]):
+        return jsonify(errno=RET.PARAMERR, errmsg="参数有误")
+
+    if gender not in (["MAN", "WOMAN"]):
+        return jsonify(errno=RET.PARAMERR, errmsg="参数有误")
+
+    # 3.更新并保存数据
+    user.nick_name = nick_name
+    user.gender = gender
+    user.signature = signature
+    try:
+        db.session.commit()
+    except Exception as e:
+        current_app.logger.error(e)
+        db.session.rollback()
+        return jsonify(errno=RET.DBERR, errmsg="保存数据失败")
+
+    # 讲session中保存的数据进行事实更新
+    session["nick_name"] = nick_name
+    return jsonify(errno=RET.OK, errmsg="更新成功")
+
+
+@profile_bp.route("/pic_info", methods=["GET", "POST"])
+@user_login_data
+def pic_info():
+    user = g.user
+    if request.method == "GET":
+        return render_template("profile/user_pic_info.html", data={"user_info": user.to_dict()})
+
+    # 1.获取到上传的文件
+    try:
+        avatar_file = request.files.get("avatar").read()
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(errno=RET.PARAMERR, errmsg="读取文件出错")
+
+    # 2.再将文件上传到奥七牛云
+    try:
+        url = pic_storage(avatar_file)
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(errno=RET.THIRDERR, errmsg="上传图片错误")
+
+    # 设置用户模块相关数据
+    user.avatar_url = url
+    # 将数据保存到数据库
+    try:
+        db.session.commit()
+    except Exception as e:
+        current_app.logger.error(e)
+        db.session.rollback()
+        return jsonify(errno=RET.DBERR, errmsg="数据存储有误")
+
+    # 4.返回上传的结果<avatar_url>
+    return jsonify(errno=RET.OK, errmsg="图片保存成功",
+                   data={"avatar_url": constants.QINIU_DOMIN_PREFIX + url})
+
+
+@profile_bp.route("/pass_info", methods=["GET", "POST"])
+@user_login_data
+def pass_info():
+    if request.method == "GET":
+        return render_template("profile/user_pass_info.html")
+
+    # 1.获取到传入参数
+    data_dict = request.json
+    old_password = data_dict.get("old_password")
+    new_password = data_dict.get("new_password")
+
+    if not all([old_password, new_password]):
+        return jsonify(errno=RET.PARAMERR, errmsg="参数有误")
+
+    # 2.获取当前登录用户的信息
+    user = g.user
+
+    if not user.check_passowrd(old_password):
+        return jsonify(errno=RET.PWDERR, errmsg="原密码有误")
+
+    # 更新数据
+    user.password = new_password
+    try:
+        db.session.commit()
+    except Exception as e:
+        current_app.logger.error(e)
+        db.session.rollback()
+        return jsonify(errno=RET.DBERR, errmsg="保存数据失败")
+
+    return jsonify(errno=RET.OK, errmsg="保存成功")
+
+
+@profile_bp.route("/collection")
+@user_login_data
+def user_collection():
+    # 获取页数
+    page_num = request.args.get("p", 1)
+    try:
+        page_num = int(page_num)
+    except Exception as e:
+        current_app.logger.error(e)
+        page_num = 1
+
+    user = g.user
+    collections = []
+    current_page = 1
+    total_page = 1
+
+    if user:
+        try:
+            # 进行分页数据查询
+            paginate = user.collection_news.paginate(page_num, constants.USER_COLLECTION_MAX_NEWS, False)
+            # 获取分页数据
+            collections = paginate.items
+            # 获取当前页
+            current_page = paginate.page
+            # 获取总页数
+            total_page = paginate.pages
+        except Exception as e:
+            current_app.logger.error(e)
+
+        collection_dict_li = []
+        for news in collections:
+            collection_dict_li.append(news.to_dict())
+
+    data = {
+        "total_page": total_page,
+        "current_page": current_page,
+        "collections": collection_dict_li
+    }
+
+    return render_template("profile/user_collection.html", data=data)
+
+
+@profile_bp.route("/news_release", methods=["GET", "POST"])
+@user_login_data
+def news_release():
+    if request.method == "GET":
+        categories = []
+        try:
+            # 获取所有的分类数据
+            categories = Category.query.all()
+        except Exception as e:
+            current_app.logger.error(e)
+
+        # 定义列表保存分类数据
+        categories_dict_list = []
+
+        for category in categories:
+            # 获取字典
+            cate_dict = category.to_dict()
+            # 拼接内容
+            categories_dict_list.append(cate_dict)
+
+        # 移除"最新"分类
+        categories_dict_list.pop(0)
+
+        data = {
+            "categories": categories_dict_list
+        }
+        # 返回内容
+        return render_template('profile/user_news_release.html', data=data)
+
+    # POST提交,执行发布新闻操作
+
+    # 1.获取要提交的数据
+    title = request.form.get("title")
+    source = "个人发布"
+    digest = request.form.get("digest")
+    content = request.form.get("content")
+    index_image = request.files.get("index_image")
+    category_id = request.form.get("category_id")
+    # 1.1判断数据是否有值
+    if not all([title, source, digest, content, index_image, category_id]):
+        return jsonify(errno=RET.PARAMERR, errmsg="参数有误")
+
+    # 1.2尝试读取图片
+    try:
+        index_image = index_image.read()
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(errno=RET.PARAMERR, errmsg="参数有误")
+
+    # 2.将标题图片上传到七牛
+    try:
+        key = pic_storage(index_image)
+    except Exception as e:
+        current_app.logger.error(e)
+        return jsonify(errno=RET.THIRDERR, errmsg="上传图片失败")
+
+    # 3.初始化新闻模型,并设置相关数据
+    news = News()
+    news.title = title
+    news.digest = digest
+    news.source = source
+    news.content = content
+    news.index_image_url = constants.QINIU_DOMIN_PREFIX + key
+    news.category_id = category_id
+    news.user_id = g.user.id
+    # 1代表待审核状态
+    news.status = 1
+
+    # 4.保存到数据库
+    try:
+        db.session.add(news)
+        db.session.commit()
+    except Exception as e:
+        current_app.logger.error(e)
+        db.session.rollback()
+        return jsonify(errno=RET.DBERR, errmsg="保存数据失败")
+
+    # 5.返回结果
+    return jsonify(errno=RET.OK, errmsg="发布成功,等待审核")
+
+
+@profile_bp.route("/news_list")
+@user_login_data
+def news_list():
+
+    #获取页数
+    page_num = request.args.get("p", 1)
+    try:
+        page_num = int(page_num)
+    except Exception as e:
+        current_app.logger.error(e)
+        page_num = 1
+
+    user = g.user
+    news_list = []
+    current_page = 1
+    total_page = 1
+    try:
+        paginate = News.query.filter(News.user_id == user.id)\
+            .paginate(page_num, constants.USER_COLLECTION_MAX_NEWS, False )
+        # 获取当前页数据
+        news_list = paginate.items
+        # 获取当前页
+        current_page = paginate.page
+        # 获取总页数
+        total_page = paginate.pages
+    except Exception as e:
+        current_app.logger.error(e)
+
+    news_dict_list = []
+    for news in news_list:
+        news_dict_list.append(news.to_dict())
+
+    data = {
+        "news_list": news_dict_list,
+        "total_page": total_page,
+        "current_page": current_page
+    }
+
+    return render_template('profile/user_news_list.html', data=data)
+
+
+

+ 27 - 0
app/static/admin/css/jquery.pagination.css

@@ -0,0 +1,27 @@
+.ui-pagination-container {
+	height: 34px;
+	line-height: 34px;
+}
+
+.ui-pagination-container .ui-pagination-page-item {
+	font-size: 14px;
+	padding: 4px 10px;
+	background: #fff;
+	border: 1px solid #c5b7b7;
+	color: #888;
+	margin: 0 3px;
+	text-decoration: none;
+}
+
+.ui-pagination-container .ui-pagination-page-item:hover {
+	border-color: #568dbd;
+	color: #568dbd;
+	text-decoration: none;
+}
+
+.ui-pagination-container .ui-pagination-page-item.active {
+	background: #568dbd;
+	border-color: #568dbd;
+	color: #fff;
+	cursor: default;
+}

+ 633 - 0
app/static/admin/css/main.css

@@ -0,0 +1,633 @@
+body{
+	background:#f1f2f6;
+	font-family:'Microsoft YaHei';
+}
+
+.login_logo{
+	background:rgba(47,64,80,0.4);
+	height:45px;
+}
+
+.login_logo img{
+	margin:5px 0 0 10px;
+}
+
+.header{
+	width:100%;
+	height:60px;
+	overflow:hidden;
+	background:#2f4050;
+	position:fixed;
+	left:0px;
+	top:0px;
+}
+
+.header .logo{
+	width:194px;
+	height:33px;
+	margin:13px 0 0 20px;
+}
+
+.header .logout{
+	width:90px;
+	height:30px;
+	background:url(../images/icons.png) 10px -344px no-repeat #1ab394;
+	margin:15px 20px 0 0;
+	border-radius:4px;
+	text-indent:40px;
+	line-height:30px;
+	font-size:14px;
+	color:#fff;
+}
+
+.header .logout:hover{
+	width:88px;
+	height:28px;
+	border:1px solid #1ab394;
+	background:url(../images/icons.png) 10px -344px no-repeat #2f4050;
+	opacity:0.5;
+}
+
+.side_bar{
+	position:fixed;
+	width:210px;
+	background:#466079;
+	left:0px;
+	top:60px;
+	bottom:0px;
+}
+
+.user_info{
+	width:210px;
+	overflow:hidden;
+}
+
+.user_info img{
+	display:block;
+	margin:20px auto 0;
+	width:68px;
+	height:68px;
+	border-radius:34px;
+}
+
+.user_info p{
+	text-align:center;
+	font-size:14px;
+	color:#bdcbd8;
+	margin:10px auto;
+}
+
+.user_info em{
+	font-weight:bold;
+	color:#1ab394;
+}
+
+.menu_con{
+	width:210px;
+	margin-top:10px;
+	overflow:hidden;
+}
+
+.first_menu{
+	height:44px;
+	border-bottom:1px solid #597b9b;
+	background:url(../images/icons.png) 190px -283px no-repeat;
+}
+
+.menu_con .active{
+	background:url(../images/icons.png) 190px -283px no-repeat #2f4050;
+	border-bottom:0px;
+}
+
+.menu_con a{
+	display:block;
+	height:44px;
+	line-height:44px;
+	color:#e5f1fc;
+	font-size:12px;
+	text-indent:60px;
+}
+
+.icon01{
+	background:url(../images/icons.png) 20px 12px no-repeat;
+}
+
+.icon02{
+	background:url(../images/icons.png) 20px -38px no-repeat;
+}
+
+.icon021{
+	background:url(../images/icons.png) 50px -87px no-repeat;
+}
+
+.icon022{
+	background:url(../images/icons.png) 50px -139px no-repeat;
+}
+
+
+.icon03{
+	background:url(../images/icons.png) 20px -89px no-repeat;
+}
+
+.icon031{
+	background:url(../images/icons.png) 50px -185px no-repeat;
+}
+
+.icon032{
+	background:url(../images/icons.png) 50px -235px no-repeat;
+}
+
+.icon033{
+	background:url(../images/icons.png) 50px -336px no-repeat;
+}
+
+.icon034{
+	background:url(../images/icons.png) 50px -390px no-repeat;
+}
+
+.sub_menu{
+	display:none;
+	border-bottom:1px solid #597b9b;
+}
+
+.sub_menu li{
+	background:url(../images/icons.png) 190px -283px no-repeat #2f4050;
+}
+
+.sub_menu a{
+	text-indent:80px;
+}
+
+.show{
+	display:block;
+}
+
+.main_body{
+	position:fixed;
+	background:#f9f9f9;
+	left:210px;
+	top:60px;
+	right:0px;
+	bottom:0px;
+}
+
+.breadcrub{
+	width:96%;
+	height:35px;
+	line-height:35px;
+	background-color:#deecf9;
+	margin:20px auto 0;
+	font-size:14px;
+	border-left:5px solid #f80;
+	text-indent:10px;
+	color:#666;
+}
+
+.pannel{
+	width:96%;
+	overflow:hidden;
+	margin:20px auto 0;
+	background-color:#fff;
+}
+
+.pannel img{
+	display:block;
+	width:90%;
+	margin:20px auto 0;
+}
+
+.spannels{
+	overflow:hidden;
+	margin:20px auto 0;
+}
+
+.spannel{
+	width:31%;
+	float:left;
+	margin-left:2%;
+	height:100px;
+	overflow:hidden;
+	text-align:center;
+	position:relative;
+}
+
+.spannel2{
+	width:30%;
+	float:left;
+	margin-left:2%;
+	height:100px;
+	overflow:hidden;
+	text-align:center;
+	position:relative;
+}
+
+.spannel em,.spannel2 em{
+	font-size:50px;
+	line-height:50px;
+	display:inline-block;
+	margin:10px 0 0 20px;
+	font-family:'Arial';
+	color:rgba(255,255,255,0.8);
+}
+
+.spannel span,.spannel2 span{
+	font-size:14px;
+	display:inline-block;
+	color:rgba(255,255,255,0.8);
+	margin-left:10px;
+}
+
+.spannel b,.spannel2 b{
+	position:absolute;
+	left:0;
+	bottom:0;
+	width:100%;
+	line-height:24px;
+	background:rgba(0,0,0,0.1);
+	color:#fff;
+	font-size:14px;
+	font-weight:normal;
+}
+
+
+
+.scolor01{
+	background-color:#ff9565;
+}
+
+.scolor02{
+	background-color:#9ed367;
+}
+
+.scolor03{
+	background-color:#23b7e5;
+}
+
+.chart_show{
+	width:100%;
+	height:400px;
+}
+
+.login_bg{
+	position:fixed;
+	left:0;
+	top:0;
+	width:100%;
+	height:100%;
+	z-index:-9999;
+	background:url(../images/login-bg.jpg) no-repeat;
+}
+
+.login_form{
+  width:330px;
+  height:292px;
+  background:#fff;
+  position:fixed;
+  left:50%;
+  top:50%;
+  margin-top:-146px;
+  margin-left:-165px;
+  overflow:hidden;
+  border-radius:6px;
+}
+
+.login_form .error_tip{
+	position:absolute;
+	font-size:12px;
+	color:#f00;
+	bottom:84px;
+	left:18px;
+	display:none;
+}
+
+
+.login_title{
+  line-height:72px;
+  text-align:center;
+  background:#1ab394;
+  color:#fff;
+  font-size:20px;
+}
+
+.input_txt{
+  display:block;
+  margin:20px auto 0;
+  width:298px;
+  height:32px;
+  border:1px solid #d0d0d0;
+  border-radius:4px;
+  outline:none;
+  text-indent:10px; 
+}
+
+.forget_pass{
+  display:block;
+  width:298px;
+  margin:10px auto 0;
+  text-align:right;
+  font-size:14px;
+  color:#5195d0;
+}
+
+.input_sub{
+  display:block;
+  margin:40px auto 0;
+  width:298px;
+  height:32px;
+  border:0px;
+  border-radius:4px;
+  background:#1ab394;
+  color:#fff;
+  cursor:pointer;
+}
+
+.input_sub:hover{
+  opacity:0.8;
+}
+
+
+.common_table{
+	width:100%;
+	border-collapse:collapse;
+	border:1px solid #d1d1d1;
+}
+
+.common_table th,.common_table td{
+	border:1px solid #d1d1d1;
+	text-align:center;
+	padding:13px 0px;
+	font-size:12px;
+}
+
+.pagenation{      
+    padding:0px;
+    list-style:none;
+    width:960px;
+    height:40px;
+    margin:20px auto 0;
+    text-align:center;
+    font-size:0px;
+}
+
+.pagenation li{
+    display:inline-block;
+    height:26px;            
+    font-size:12px;
+    margin:7px 5px 0px;          
+}
+
+.pagenation  a{
+    line-height:26px;
+    text-decoration:none;
+    color:#393c35;
+    display:block;
+    padding:0px 10px;
+    border:1px solid #d1d1d1;
+}
+
+.pagenation a:hover{
+    background:#f80;
+    color:#fff;
+}
+
+.review,.edit,.addtype{
+	display:block;
+	width:40px;
+	line-height:20px;
+	margin:-5px auto;
+	border-radius:4px;
+	color:#fff;
+	background:#25c192;
+}
+
+.addtype{
+	width:100px;
+	line-height:26px;
+}
+
+.edit{
+	background:#25c192;
+}
+
+.common_table .tleft{
+	text-align:left;
+	text-indent:10px;
+}
+
+.review_title{
+	font-size:18px;
+	line-height:40px;
+	text-indent:20px;
+	border-bottom:1px solid #ddd;
+	margin-bottom:20px;
+}
+
+.form_group{
+	overflow:hidden;
+	margin-bottom:15px;
+}
+
+.form_group label{
+	float:left;
+	width:12%;
+	line-height:30px;
+	text-align:right;
+	font-size:14px;
+}
+
+.form_group .input_txt2{
+	width:86%;
+	height:30px;
+	float:left;
+	text-indent:10px;
+	outline:none;
+	border-radius:4px;	
+	border:1px solid #d0d0d0;
+	box-sizing:border-box;
+}
+
+.form_group .input_area,.form_group .input_multxt{
+	width:86%;
+	height:300px;
+	float:left;
+	padding:10px;
+	outline:none;
+	font-family:'Microsoft YaHei';
+	font-size:14px;
+	border-radius:4px;
+	border:1px solid #d0d0d0;
+	box-sizing:border-box;
+}
+
+.form_group .input_multxt{
+	height:50px;
+}
+
+.rich_wrap{
+	width:86%;
+}
+
+.form_group .sel_opt{
+	width:150px;
+	height:30px;
+	float:left;
+	border:1px solid #ddd;
+	border-radius:4px;
+	outline:none;
+	text-indent:10px;
+}
+
+.form_group .index_pic{
+	width:100px;
+	height:100px;
+	float:left;
+	margin:0px;
+}
+
+.group_hide{
+	display:none;
+}
+
+.indent_group{
+	text-indent:12%;
+	margin-top:15px;
+}
+
+.confirm{
+	width:80px;
+	height:30px;
+	border-radius:4px;
+	color:#fff;
+	background:#25c192;
+	border:0px;
+	outline:none;
+}
+
+.cancel{
+	width:80px;
+	height:30px;
+	border-radius:4px;
+	color:#fff;
+	background:#25c192;
+	border:0px;
+	outline:none;
+}
+
+.line_top{
+	border-top:1px solid #ddd;
+	padding:10px 0px;
+}
+
+.input_file{
+	margin-top:4px;
+}
+
+
+.pop{
+	width:500px;
+	height:270px;
+	border:1px solid #ddd;
+	background:#fff;
+	position:fixed;
+	left:50%;
+	top:50%;
+	margin-top:-151px;
+	margin-left:-251px;
+	z-index:9999;
+}
+
+.pop h3{
+	background:#25c192;
+	line-height:40px;
+	margin:2px;
+	color:#fff;
+	text-indent:10px;
+}
+
+.form_group .input_label{
+	width:80px;
+    line-height:32px;
+    margin-left:60px;
+}
+
+.form_group .error_tip{
+	position:absolute;
+	left:141px;
+	top:132px;
+	color:#f00;
+	font-size:12px;
+	display:none;
+}
+
+.input_txt3{
+  width:298px;
+  height:32px;
+  border:1px solid #d0d0d0;
+  border-radius:4px;
+  outline:none;
+  text-indent:10px; 
+}
+
+.mt50{
+	margin-top:50px;
+}
+
+.pop .line_top{
+	text-align:center;
+	margin-top:80px;
+	padding-top:15px;
+}
+
+.mask{
+	position:fixed;
+	width:100%;
+	height:100%;
+	background:#000;
+	left:0px;
+	top:0px;
+	opacity:0.3
+}
+
+.pop_con{
+	display:none;
+}
+
+.category_name{
+	font-size:14px;
+	line-height:30px;
+}
+
+.ui-pagination-container{
+	text-align:center;
+	margin-top:5px;
+}
+
+.news_filter_form{
+	float:right;
+	height:36px;
+	margin:4px 10px 0 0;
+}
+
+.news_filter_form .input_txt{
+	float:left;
+	margin:0px;
+	width:298px;
+	height:26px;
+	border:1px solid #d0d0d0;
+	border-radius:4px;
+	outline:none;
+	text-indent:10px; 
+  }
+
+  .news_filter_form .input_sub{
+	float:left;
+	margin:0px;
+	margin-left:15px;
+	width:100px;
+	height:28px;
+	border:0px;
+	border-radius:4px;
+	background:#1ab394;
+	color:#fff;
+	cursor:pointer;
+  }

+ 54 - 0
app/static/admin/css/reset.css

@@ -0,0 +1,54 @@
+/*  去掉标签默认的间距   */
+body,ul,ol,p,h1,h2,h3,h4,h4,h6,dl,dd,select,input,form{
+	margin:0;
+	padding:0;
+}
+/*  去掉小圆点以及数字   */
+ul,ol{
+	list-style:none;
+}
+/* 去掉下划线 */
+a{text-decoration:none;}
+
+/* 去掉斜体 */
+em{
+	font-style:normal;
+}
+/* 让h标签继承body的文字设置 */
+h1,h2,h3,h4,h4,h6{
+	font-size:100%;
+	font-weight:normal;
+}
+/* 在IE下去掉图片做链接时生成的框线 */
+img{
+	border:0px;
+}
+
+/* 清除浮动以及清除margin-top塌陷 */
+.clearfix:before,.clearfix:after{
+	content:'';
+	display:table;
+}
+.clearfix:after{
+	clear:both;
+}
+.clearfix{
+	zoom:1;
+}
+
+
+.fl{
+	float:left;
+}
+
+.fr{
+	float:right;
+}
+
+
+
+
+
+
+
+

+ 130 - 0
app/static/admin/html/news_edit.html

@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<title>新经资讯后台管理</title>
+	<link rel="stylesheet" type="text/css" href="../css/reset.css">
+	<link rel="stylesheet" type="text/css" href="../css/main.css">
+	<link rel="stylesheet" href="../css/jquery.pagination.css">
+	<script type="text/javascript" src="../js/jquery-1.12.4.min.js"></script>
+	<script type="text/javascript" src="../js/jquery.pagination.min.js"></script>
+</head>
+<body>
+	<div class="breadcrub">
+			当前位置:新闻管理>新闻版式编辑
+		<form class="news_filter_form">
+			<input name="keywords"  type="text" placeholder="请输入关键字" class="input_txt">
+			<input type="submit" value="搜 索" class="input_sub">
+		</form>
+	</div>
+		
+
+	<div class="pannel">			
+			<table class="common_table">
+				<tr>
+					<th width="5%">id</th>
+					<th width="70%">标题</th>
+					<th width="15%">发布时间</th>
+					<th width="10%">管理操作</th>
+				</tr>
+				<tr>
+					<td>1</td>
+					<td class="tleft">日本史上最大IPO之一要来了:软银计划将手机业务分拆上市</td>
+					<td>2018-3-5 21:39:05</td>
+					<td>
+						<a href="news_edit_detail.html" class="edit">编辑</a>
+					</td>
+				</tr>
+				<tr>
+					<td>2</td>
+					<td class="tleft">日本史上最大IPO之一要来了:软银计划将手机业务分拆上市</td>
+					<td>2018-3-5 21:39:05</td>
+					<td>
+						<a href="news_edit_detail.html" class="edit">编辑</a>
+					</td>
+				</tr>					
+				<tr>
+					<td>3</td>
+					<td class="tleft">日本史上最大IPO之一要来了:软银计划将手机业务分拆上市</td>
+					<td>2018-3-5 21:39:05</td>
+					<td>
+						<a href="news_edit_detail.html" class="edit">编辑</a>
+					</td>
+				</tr>
+				<tr>
+					<td>4</td>
+					<td class="tleft">日本史上最大IPO之一要来了:软银计划将手机业务分拆上市</td>
+					<td>2018-3-5 21:39:05</td>
+					<td>
+						<a href="news_edit_detail.html" class="edit">编辑</a>
+					</td>
+				</tr>
+				<tr>
+					<td>5</td>
+					<td class="tleft">日本史上最大IPO之一要来了:软银计划将手机业务分拆上市</td>
+					<td>2018-3-5 21:39:05</td>
+					<td>
+						<a href="news_edit_detail.html" class="edit">编辑</a>
+					</td>
+				</tr>
+				<tr>
+					<td>6</td>
+					<td class="tleft">日本史上最大IPO之一要来了:软银计划将手机业务分拆上市</td>
+					<td>2018-3-5 21:39:05</td>
+					<td>
+						<a href="news_edit_detail.html" class="edit">编辑</a>
+					</td>
+				</tr>
+				<tr>
+					<td>7</td>
+					<td class="tleft">日本史上最大IPO之一要来了:软银计划将手机业务分拆上市</td>
+					<td>2018-3-5 21:39:05</td>
+					<td>
+						<a href="news_edit_detail.html" class="edit">编辑</a>
+					</td>
+				</tr>
+				<tr>
+					<td>8</td>
+					<td class="tleft">日本史上最大IPO之一要来了:软银计划将手机业务分拆上市</td>
+					<td>2018-3-5 21:39:05</td>
+					<td>
+						<a href="news_edit_detail.html" class="edit">编辑</a>
+					</td>
+				</tr>
+				<tr>
+					<td>9</td>
+					<td class="tleft">日本史上最大IPO之一要来了:软银计划将手机业务分拆上市</td>
+					<td>2018-3-5 21:39:05</td>
+					<td>
+						<a href="news_edit_detail.html" class="edit">编辑</a>
+					</td>
+				</tr>
+				<tr>
+					<td>10</td>
+					<td class="tleft">日本史上最大IPO之一要来了:软银计划将手机业务分拆上市</td>
+					<td>2018-3-5 21:39:05</td>
+					<td>
+						<a href="news_edit_detail.html" class="edit">编辑</a>
+					</td>
+				</tr>
+			</table>
+		</div>
+		
+		<div class="box">
+			<div id="pagination" class="page"></div>
+		</div>
+
+        <script>
+			$(function() {
+				$("#pagination").pagination({
+					currentPage: 2,
+					totalPage: 3,
+					callback: function(current) {
+					    alert('ok!');
+					}
+				});
+			});
+		</script>
+	
+</body>
+</html>

+ 59 - 0
app/static/admin/html/news_edit_detail.html

@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<title>新经资讯后台管理</title>
+	<link rel="stylesheet" type="text/css" href="../css/reset.css">
+	<link rel="stylesheet" type="text/css" href="../css/main.css">
+	<script type="text/javascript" src="../js/jquery-1.12.4.min.js"></script>
+	<script type="text/javascript" src="../js/jquery.form.min.js"></script>
+	<script src="../tinymce/js/tinymce/tinymce.min.js"></script>
+    <script src="../js/tinymce_setup.js"></script>
+	<script src="../js/news_edit_detail.js"></script>
+</head>
+<body>
+	<div class="breadcrub">
+			当前位置:新闻管理>新闻版本编辑			
+		</div>
+		<div class="pannel">
+			<form class="news_edit">
+			<h3 class="review_title">新闻版式编辑</h3>
+	    	<div class="form_group">
+	    		<label>新闻标题:</label>
+	    		<input type="text" class="input_txt2" value="日本史上最大IPO之一要来了:软银计划将手机业务分拆上市">
+	    	</div>
+	    	<div class="form_group">
+				<label>新闻分类:</label>
+				<select class="sel_opt">
+					<option value="">分类一</option>
+					<option value="">分类二</option>
+					<option value="">分类三</option>
+				</select>
+			</div>
+			<div class="form_group">
+				<label>新闻摘要:</label>
+				<textarea class="input_multxt">据日经新闻网,软银计划让旗下核心业务移动手机部门SoftBank Corp.分拆上市,或募资2万亿日元(约180亿美元。随着软银逐步向投资公司转型,此举旨在给手机业务部门更多自主权。</textarea>
+			</div>
+			<div class="form_group">
+				<label>索引图片:</label>
+                <img src="" alt="索引图片" class="index_pic">
+			</div>
+			<div class="form_group">
+				<label>上传图片:</label>
+				<input type="file" class="input_file">
+			</div>
+			<div class="form_group">
+				<label>新闻内容:</label>
+				<div class="rich_wrap fl">
+					<input class="input_area" id="rich_content" name="content" value="哈哈"></input>
+				</div>
+			</div>
+			<div class="form_group indent_group line_top">
+				<input type="submit" value="确定" class="confirm">
+				<input type="button" value="取消" class="cancel" onclick="cancel()">
+			</div>
+			</form>	
+		</div>
+
+</body>
+</html>

+ 70 - 0
app/static/admin/html/news_type.html

@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<title>新经资讯后台管理</title>
+	<link rel="stylesheet" type="text/css" href="../css/reset.css">
+	<link rel="stylesheet" type="text/css" href="../css/main.css">
+	<script type="text/javascript" src="../js/jquery-1.12.4.min.js"></script>
+	<script type="text/javascript" src="../js/news_type.js"></script>
+</head>
+<body>
+	<div class="breadcrub">
+			当前位置:新闻管理>新闻分类管理
+		</div>
+		<div class="pannel">
+			<table class="common_table">
+				<tr>
+					<th width="10%">id</th>
+					<th width="80%">类别名称</th>
+					<th width="10%">管理操作</th>
+				</tr>
+				<tr>
+					<td>1</td>
+					<td>股市</td>
+					<td><a href="javascript:;" class="edit">编辑</a></td>
+				</tr>
+				<tr>
+					<td>2</td>
+					<td>债市</td>
+					<td><a href="javascript:;" class="edit">编辑</a></td>
+				</tr>
+				<tr>
+					<td>3</td>
+					<td>商品</td>
+					<td><a href="javascript:;" class="edit">编辑</a></td>
+				</tr>
+				<tr>
+					<td>4</td>
+					<td>外汇</td>
+					<td><a href="javascript:;" class="edit">编辑</a></td>
+				</tr>
+				<tr>
+					<td>5</td>
+					<td>公司</td>
+					<td><a href="javascript:;" class="edit">编辑</a></td>
+				</tr>
+				<tr>
+					<td colspan="3"><a href="javascript:;" class="addtype">增加分类</a></td>
+				</tr>
+			</table>
+		</div>
+		
+		<div class="pop_con">
+			<div class="pop">
+				<form>
+					<h3>修改分类</h3>
+					<div class="form_group mt50">
+						<label class="input_label">分类名称:</label><input type="text" class="input_txt3">
+						<span class="error_tip">提示文字</span>
+					</div>			
+					<div class="form_group line_top">
+						<input type="button" value="确定" class="confirm">&nbsp;&nbsp;&nbsp;&nbsp;
+						<input type="button" value="取消" class="cancel">
+					</div>
+				</form>
+			</div>
+			<div class="mask"></div>
+		</div>	
+</body>
+</html>

BIN
app/static/admin/images/icons.png


BIN
app/static/admin/images/login-bg.jpg


BIN
app/static/admin/images/logo.png


BIN
app/static/admin/images/person.png


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/js/echarts.min.js


File diff suppressed because it is too large
+ 1 - 0
app/static/admin/js/jquery-1.12.4.min.js


File diff suppressed because it is too large
+ 10 - 0
app/static/admin/js/jquery.form.min.js


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/js/jquery.pagination.min.js


+ 16 - 0
app/static/admin/js/news_edit_detail.js

@@ -0,0 +1,16 @@
+function getCookie(name) {
+    var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
+    return r ? r[1] : undefined;
+}
+
+$(function(){
+    $(".news_edit").submit(function (e) {
+        e.preventDefault()
+        // TODO 新闻编辑提交
+    })
+})
+
+// 点击取消,返回上一页
+function cancel() {
+    history.go(-1)
+}

+ 60 - 0
app/static/admin/js/news_review_detail.js

@@ -0,0 +1,60 @@
+function getCookie(name) {
+    var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
+    return r ? r[1] : undefined;
+}
+
+$(function(){
+    $(".news_review").submit(function (e) {
+        e.preventDefault()
+
+        // TODO 新闻审核提交
+
+        // 获取到所有的参数
+        var params = {};
+
+        $(this).serializeArray().map(function (x) {
+            params[x.name] = x.value;
+        });
+
+        // 取到参数以便判断
+        var action = params["action"];
+        var news_id = params["news_id"];
+        var reason = params["reason"];
+
+        if (action == "reject" && !reason){
+            alert("请输入拒绝原因");
+            return;
+        }
+
+        params = {
+            "action": action,
+            "news_id": news_id,
+            "reason": reason
+        };
+
+        $.ajax({
+            url: "/admin/news_review_detail",
+            type:"post",
+            contentType: "application/json",
+            headers:{
+                "X-CSRFToken": getCookie("csrf_token")
+            },
+            data: JSON.stringify(params),
+            success:function (resp) {
+                if (resp.errno == "0"){
+                    // 返回上一页,刷新数据
+                    location.href = document.referrer;
+                }else {
+                    alert(resp.errmsg);
+                }
+            }
+        })
+
+
+    })
+})
+
+// 点击取消,返回上一页
+function cancel() {
+    history.go(-1)
+}

+ 73 - 0
app/static/admin/js/news_type.js

@@ -0,0 +1,73 @@
+function getCookie(name) {
+    var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
+    return r ? r[1] : undefined;
+}
+
+$(function(){
+    var $a = $('.edit');
+    var $add = $('.addtype');
+    var $pop = $('.pop_con');
+    var $cancel = $('.cancel');
+    var $confirm = $('.confirm');
+    var $error = $('.error_tip');
+    var $input = $('.input_txt3');
+    var sHandler = 'edit';
+    var sId = 0;
+
+    $a.click(function(){
+        sHandler = 'edit';
+        sId = $(this).parent().siblings().eq(0).html();
+        $pop.find('h3').html('修改分类');
+        $pop.find('.input_txt3').val($(this).parent().prev().html());
+        $pop.show();
+    });
+
+    $add.click(function(){
+        sHandler = 'add';
+        $pop.find('h3').html('新增分类');
+        $input.val('');
+        $pop.show();
+    });
+
+    $cancel.click(function(){
+        $pop.hide();
+        $error.hide();
+    });
+
+    $input.click(function(){
+        $error.hide();
+    });
+
+    $confirm.click(function(){
+
+        var params = {}
+        if(sHandler=='edit')
+        {
+            var sVal = $input.val();
+            if(sVal=='')
+            {
+                $error.html('输入框不能为空').show();
+                return;
+            }
+            params = {
+                "id": sId,
+                "name": sVal,
+            };
+        }
+        else
+        {
+            var sVal = $input.val();
+            if(sVal=='')
+            {
+                $error.html('输入框不能为空').show();
+                return;
+            }
+            params = {
+                "name": sVal,
+            }
+        }
+
+        // TODO 发起修改分类请求
+
+    })
+})

+ 34 - 0
app/static/admin/js/tinymce_setup.js

@@ -0,0 +1,34 @@
+tinymce.init({
+    //选择class为content的标签作为编辑器
+    selector: '#rich_content',
+    //方向从左到右
+    directionality:'ltr',
+    //语言选择中文
+    language:'zh_CN',
+    //高度为400
+    height:400,
+    width: '100%',
+    //工具栏上面的补丁按钮
+    plugins: [
+            'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker',
+            'searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking',
+            'save table contextmenu directionality template paste textcolor',
+            'codesample imageupload',
+    ],
+    //工具栏的补丁按钮
+     toolbar: 'insertfile undo redo | \
+     styleselect | \
+     bold italic | \
+     alignleft aligncenter alignright alignjustify | \
+     bullist numlist outdent indent | \
+     link image | \
+     print preview media fullpage | \
+     forecolor backcolor emoticons |\
+     codesample fontsizeselect fullscreen |\
+     imageupload',
+    //字体大小
+    fontsize_formats: '10pt 12pt 14pt 18pt 24pt 36pt',
+    //按tab不换行
+    nonbreaking_force_tab: true,
+    imageupload_url: "/user/submit-image"
+});

+ 504 - 0
app/static/admin/tinymce/LICENSE.TXT

@@ -0,0 +1,504 @@
+      GNU LESSER GENERAL PUBLIC LICENSE
+           Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+          Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+      GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+          NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+         END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+

+ 916 - 0
app/static/admin/tinymce/changelog.txt

@@ -0,0 +1,916 @@
+Version 4.7.9 (2018-02-27)
+    Fixed a bug where the editor target element didn't get the correct style when removing the editor.
+Version 4.7.8 (2018-02-26)
+    Fixed an issue with the Help Plugin where the menuitem name wasn't lowercase.
+    Fixed an issue on MacOS where text and bold text did not have the same line-height in the autocomplete dropdown in the Link Plugin dialog.
+    Fixed a bug where the "paste as text" option in the Paste Plugin didn't work.
+    Fixed a bug where dialog list boxes didn't get positioned correctly in documents with scroll.
+    Fixed a bug where the Inlite Theme didn't use the Table Plugin api to insert correct tables.
+    Fixed a bug where the Inlite Theme panel didn't hide on blur in a correct way.
+    Fixed a bug where placing the cursor before a table in Firefox would scroll to the bottom of the table.
+    Fixed a bug where selecting partial text in table cells with rowspans and deleting would produce faulty tables.
+    Fixed a bug where the Preview Plugin didn't work on Safari due to sandbox security.
+    Fixed a bug where table cell selection using the keyboard threw an error.
+    Fixed so the font size and font family doesn't toggle the text but only sets the selected format on the selected text.
+    Fixed so the built-in spellchecking on Chrome and Safari creates an undo level when replacing words.
+Version 4.7.7 (2018-02-19)
+    Added a border style selector to the advanced tab of the Image Plugin.
+    Added better controls for default table inserted by the Table Plugin.
+    Added new `table_responsive_width` option to the Table Plugin that controls whether to use pixel or percentage widths.
+    Fixed a bug where the Link Plugin text didn't update when a URL was pasted using the context menu.
+    Fixed a bug with the Spellchecker Plugin where using "Add to dictionary" in the context menu threw an error.
+    Fixed a bug in the Media Plugin where the preview node for iframes got default width and height attributes that interfered with width/height styles.
+    Fixed a bug where backslashes were being added to some font family names in Firefox in the fontselect toolbar item.
+    Fixed a bug where errors would be thrown when trying to remove an editor that had not yet been fully initialized.
+    Fixed a bug where the Imagetools Plugin didn't update the images atomically.
+    Fixed a bug where the Fullscreen Plugin was throwing errors when being used on an inline editor.
+    Fixed a bug where drop down menus weren't positioned correctly in inline editors on scroll.
+    Fixed a bug with a semicolon missing at the end of the bundled javascript files.
+    Fixed a bug in the Table Plugin with cursor navigation inside of tables where the cursor would sometimes jump into an incorrect table cells.
+    Fixed a bug where indenting a table that is a list item using the "Increase indent" button would create a nested table.
+    Fixed a bug where text nodes containing only whitespace were being wrapped by paragraph elements.
+    Fixed a bug where whitespace was being inserted after br tags inside of paragraph tags.
+    Fixed a bug where converting an indented paragraph to a list item would cause the list item to have extra padding.
+    Fixed a bug where Copy/Paste in an editor with a lot of content would cause the editor to scroll to the top of the content in IE11.
+    Fixed a bug with a memory leak in the DragHelper. Path contributed by ben-mckernan.
+    Fixed a bug where the advanced tab in the Media Plugin was being shown even if it didn't contain anything. Patch contributed by gabrieeel.
+    Fixed an outdated eventname in the EventUtils. Patch contributed by nazar-pc.
+    Fixed an issue where the Json.parse function would throw an error when being used on a page with strict CSP settings.
+    Fixed so you can place the curser before and after table elements within the editor in Firefox and Edge/IE.
+Version 4.7.6 (2018-01-29)
+    Fixed a bug in the jquery integration where it threw an error saying that "global is not defined".
+    Fixed a bug where deleting a table cell whose previous sibling was set to contenteditable false would create a corrupted table.
+    Fixed a bug where highlighting text in an unfocused editor did not work correctly in IE11/Edge.
+    Fixed a bug where the table resize handles were not being repositioned when activating the Fullscreen Plugin.
+    Fixed a bug where the Imagetools Plugin dialog didn't honor editor RTL settings.
+    Fixed a bug where block elements weren't being merged correctly if you deleted from after a contenteditable false element to the beginning of another block element.
+    Fixed a bug where TinyMCE didn't work with module loaders like webpack.
+Version 4.7.5 (2018-01-22)
+    Fixed bug with the Codesample Plugin where it wasn't possible to edit codesamples when the editor was in inline mode.
+    Fixed bug where focusing on the status bar broke the keyboard navigation functionality.
+    Fixed bug where an error would be thrown on Edge by the Table Plugin when pasting using the PowerPaste Plugin.
+    Fixed bug in the Table Plugin where selecting row border style from the dropdown menu in advanced row properties would throw an error.
+    Fixed bug with icons being rendered incorrectly on Chrome on Mac OS.
+    Fixed bug in the Textcolor Plugin where the font color and background color buttons wouldn't trigger an ExecCommand event.
+    Fixed bug in the Link Plugin where the url field wasn't forced LTR.
+    Fixed bug where the Nonbreaking Plugin incorrectly inserted spaces into tables.
+    Fixed bug with the inline theme where the toolbar wasn't repositioned on window resize.
+Version 4.7.4 (2017-12-05)
+    Fixed bug in the Nonbreaking Plugin where the nonbreaking_force_tab setting was being ignored.
+    Fixed bug in the Table Plugin where changing row height incorrectly converted column widths to pixels.
+    Fixed bug in the Table Plugin on Edge and IE11 where resizing the last column after resizing the table would cause invalid column heights.
+    Fixed bug in the Table Plugin where keyboard navigation was not normalized between browsers.
+    Fixed bug in the Table Plugin where the colorpicker button would show even without defining the colorpicker_callback.
+    Fixed bug in the Table Plugin where it wasn't possible to set the cell background color.
+    Fixed bug where Firefox would throw an error when intialising an editor on an element that is hidden or not yet added to the DOM.
+    Fixed bug where Firefox would throw an error when intialising an editor inside of a hidden iframe.
+Version 4.7.3 (2017-11-23)
+    Added functionality to open the Codesample Plugin dialog when double clicking on a codesample. Patch contributed by dakuzen.
+    Fixed bug where undo/redo didn't work correctly with some formats and caret positions.
+    Fixed bug where the color picker didn't show up in Table Plugin dialogs.
+    Fixed bug where it wasn't possible to change the width of a table through the Table Plugin dialog.
+    Fixed bug where the Charmap Plugin couldn't insert some special characters.
+    Fixed bug where editing a newly inserted link would not actually edit the link but insert a new link next to it.
+    Fixed bug where deleting all content in a table cell made it impossible to place the caret into it.
+    Fixed bug where the vertical alignment field in the Table Plugin cell properties dialog didn't do anything.
+    Fixed bug where an image with a caption showed two sets of resize handles in IE11.
+    Fixed bug where pressing the enter button inside of an h1 with contenteditable set to true would sometimes produce a p tag.
+    Fixed bug with backspace not working as expected before a noneditable element.
+    Fixed bug where operating on tables with invalid rowspans would cause an error to be thrown.
+    Fixed so a real base64 representation of the image is available on the blobInfo that the images_upload_handler gets called with.
+    Fixed so the image upload tab is available when the images_upload_handler is defined (and not only when the images_upload_url is defined).
+Version 4.7.2 (2017-11-07)
+    Added newly rewritten Table Plugin.
+    Added support for attributes with colon in valid_elements and addValidElements.
+    Added support for dailymotion short url in the Media Plugin. Patch contributed by maat8.
+    Added support for converting to half pt when converting font size from px to pt. Patch contributed by danny6514.
+    Added support for location hash to the Autosave plugin to make it work better with SPAs using hash routing.
+    Added support for merging table cells when pasting a table into another table.
+    Changed so the language packs are only loaded once. Patch contributed by 0xor1.
+    Simplified the css for inline boundaries selection by switching to an attribute selector.
+    Fixed bug where an error would be thrown on editor initialization if the window.getSelection() returned null.
+    Fixed bug where holding down control or alt keys made the keyboard navigation inside an inline boundary not work as expected.
+    Fixed bug where applying formats in IE11 produced extra, empty paragraphs in the editor.
+    Fixed bug where the Word Count Plugin didn't count some mathematical operators correctly.
+    Fixed bug where removing an inline editor removed the element that the editor had been initialized on.
+    Fixed bug where setting the selection to the end of an editable container caused some formatting problems.
+    Fixed bug where an error would be thrown sometimes when an editor was removed because of the selection bookmark was being stored asynchronously.
+    Fixed a bug where an editor initialized on an empty list did not contain any valid cursor positions.
+    Fixed a bug with the Context Menu Plugin and webkit browsers on Mac where right-clicking inside a table would produce an incorrect selection.
+    Fixed bug where the Image Plugin constrain proportions setting wasn't working as expected.
+    Fixed bug where deleting the last character in a span with decorations produced an incorrect element when typing.
+    Fixed bug where focusing on inline editors made the toolbar flicker when moving between elements quickly.
+    Fixed bug where the selection would be stored incorrectly in inline editors when the mouseup event was fired outside the editor body.
+    Fixed bug where toggling bold at the end of an inline boundary would toggle off the whole word.
+    Fixed bug where setting the skin to false would not stop the loading of some skin css files.
+    Fixed bug in mobile theme where pinch-to-zoom would break after exiting the editor.
+    Fixed bug where sublists of a fully selected list would not be switched correctly when changing list style.
+    Fixed bug where inserting media by source would break the UndoManager.
+    Fixed bug where inserting some content into the editor with a specific selection would replace some content incorrectly.
+    Fixed bug where selecting all content with ctrl+a in IE11 caused problems with untoggling some formatting.
+    Fixed bug where the Search and Replace Plugin left some marker spans in the editor when undoing and redoing after replacing some content.
+    Fixed bug where the editor would not get a scrollbar when using the Fullscreen and Autoresize plugins together.
+    Fixed bug where the font selector would stop working correctly after selecting fonts three times.
+    Fixed so pressing the enter key inside of an inline boundary inserts a br after the inline boundary element.
+    Fixed a bug where it wasn't possible to use tab navigation inside of a table that was inside of a list.
+    Fixed bug where end_container_on_empty_block would incorrectly remove elements.
+    Fixed bug where content_styles weren't added to the Preview Plugin iframe.
+    Fixed so the beforeSetContent/beforeGetContent events are preventable.
+    Fixed bug where changing height value in Table Plugin advanced tab didn't do anything.
+    Fixed bug where it wasn't possible to remove formatting from content in beginning of table cell.
+Version 4.7.1 (2017-10-09)
+    Fixed bug where theme set to false on an inline editor produced an extra div element after the target element.
+    Fixed bug where the editor drag icon was misaligned with the branding set to false.
+    Fixed bug where doubled menu items were not being removed as expected with the removed_menuitems setting.
+    Fixed bug where the Table of contents plugin threw an error when initialized.
+    Fixed bug where it wasn't possible to add inline formats to text selected right to left.
+    Fixed bug where the paste from plain text mode did not work as expected.
+    Fixed so the style previews do not set color and background color when selected.
+    Fixed bug where the Autolink plugin didn't work as expected with some formats applied on an empty editor.
+    Fixed bug where the Textpattern plugin were throwing errors on some patterns.
+    Fixed bug where the Save plugin saved all editors instead of only the active editor. Patch contributed by dannoe.
+Version 4.7.0 (2017-10-03)
+    Added new mobile ui that is specifically designed for mobile devices.
+    Updated the default skin to be more modern and white since white is preferred by most implementations.
+    Restructured the default menus to be more similar to common office suites like Google Docs.
+    Fixed so theme can be set to false on both inline and iframe editor modes.
+    Fixed bug where inline editor would add/remove the visualblocks css multiple times.
+    Fixed bug where selection wouldn't be properly restored when editor lost focus and commands where invoked.
+    Fixed bug where toc plugin would generate id:s for headers even though a toc wasn't inserted into the content.
+    Fixed bug where is wasn't possible to drag/drop contents within the editor if paste_data_images where set to true.
+    Fixed bug where getParam and close in WindowManager would get the first opened window instead of the last opened window.
+    Fixed bug where delete would delete between cells inside a table in Firefox.
+Version 4.6.7 (2017-09-18)
+    Fixed bug where paste wasn't working in IOS.
+    Fixed bug where the Word Count Plugin didn't count some mathematical operators correctly.
+    Fixed bug where inserting a list in a table caused the cell to expand in height.
+    Fixed bug where pressing enter in a list located inside of a table deleted list items instead of inserting new list item.
+    Fixed bug where copy and pasting table cells produced inconsistent results.
+    Fixed bug where initializing an editor with an ID of 'length' would throw an exception.
+    Fixed bug where it was possible to split a non merged table cell.
+    Fixed bug where copy and pasting a list with a very specific selection into another list would produce a nested list.
+    Fixed bug where copy and pasting ordered lists sometimes produced unordered lists.
+    Fixed bug where padded elements inside other elements would be treated as empty.
+    Added some missing translations to Image, Link and Help plugins.
+    Fixed so you can resize images inside a figure element.
+    Fixed bug where an inline TinyMCE editor initialized on a table did not set selection on load in Chrome.
+    Fixed the positioning of the inlite toolbar when the target element wasn't big enough to fit the toolbar.
+Version 4.6.6 (2017-08-30)
+    Fixed so that notifications wrap long text content instead of bleeding outside the notification element.
+    Fixed so the content_style css is added after the skin and custom stylesheets.
+    Fixed bug where it wasn't possible to remove a table with the Cut button.
+    Fixed bug where the center format wasn't getting the same font size as the other formats in the format preview.
+    Fixed bug where the wordcount plugin wasn't counting hyphenated words correctly.
+    Fixed bug where all content pasted into the editor was added to the end of the editor.
+    Fixed bug where enter keydown on list item selection only deleted content and didn't create a new line.
+    Fixed bug where destroying the editor while the content css was still loading caused error notifications on Firefox.
+    Fixed bug where undoing cut operation in IE11 left some unwanted html in the editor content.
+    Fixed bug where enter keydown would throw an error in IE11.
+    Fixed bug where duplicate instances of an editor were added to the editors array when using the createEditor API.
+    Fixed bug where the formatter applied formats on the wrong content when spellchecker was activated.
+    Fixed bug where switching formats would reset font size on child nodes.
+    Fixed bug where the table caption element weren't always the first descendant to the table tag.
+    Fixed bug where pasting some content into the editor on chrome some newlines were removed.
+    Fixed bug where it wasn't possible to remove a list if a list item was a table element.
+    Fixed bug where copy/pasting partial selections of tables wouldn't produce a proper table.
+    Fixed bug where the searchreplace plugin could not find consecutive spaces.
+    Fixed bug where background color wasn't applied correctly on some partially selected contents.
+Version 4.6.5 (2017-08-02)
+    Added new inline_boundaries_selector that allows you to specify the elements that should have boundaries.
+    Added new local upload feature this allows the user to upload images directly from the image dialog.
+    Added a new api for providing meta data for plugins. It will show up in the help dialog if it's provided.
+    Fixed so that the notifications created by the notification manager are more screen reader accessible.
+    Fixed bug where changing the list format on multiple selected lists didn't change all of the lists.
+    Fixed bug where the nonbreaking plugin would insert multiple undo levels when pressing the tab key.
+    Fixed bug where delete/backspace wouldn't render a caret when all editor contents where deleted.
+    Fixed bug where delete/backspace wouldn't render a caret if the deleted element was a single contentEditable false element.
+    Fixed bug where the wordcount plugin wouldn't count words correctly if word where typed after applying a style format.
+    Fixed bug where the wordcount plugin would count mathematical formulas as multiple words for example 1+1=2.
+    Fixed bug where formatting of triple clicked blocks on Chrome/Safari would result in styles being added outside the visual selection.
+    Fixed bug where paste would add the contents to the end of the editor area when inline mode was used.
+    Fixed bug where toggling off bold formatting on text entered in a new paragraph would add an extra line break.
+    Fixed bug where autolink plugin would only produce a link on every other consecutive link on Firefox.
+    Fixed bug where it wasn't possible to select all contents if the content only had one pre element.
+    Fixed bug where sizzle would produce lagging behavior on some sites due to repaints caused by feature detection.
+    Fixed bug where toggling off inline formats wouldn't include the space on selected contents with leading or trailing spaces.
+    Fixed bug where the cut operation in UI wouldn't work in Chrome.
+    Fixed bug where some legacy editor initialization logic would throw exceptions about editor settings not being defined.
+    Fixed bug where it wasn't possible to apply text color to links if they where part of a non collapsed selection.
+    Fixed bug where an exception would be thrown if the user selected a video element and then moved the focus outside the editor.
+    Fixed bug where list operations didn't work if there where block elements inside the list items.
+    Fixed bug where applying block formats to lists wrapped in block elements would apply to all elements in that wrapped block.
+Version 4.6.4 (2017-06-13)
+    Fixed bug where the editor would move the caret when clicking on the scrollbar next to a content editable false block.
+    Fixed bug where the text color select dropdowns wasn't placed correctly when they didn't fit the width of the screen.
+    Fixed bug where the default editor line height wasn't working for mixed font size contents.
+    Fixed bug where the content css files for inline editors were loaded multiple times for multiple editor instances.
+    Fixed bug where the initial value of the font size/font family dropdowns wasn't displayed.
+    Fixed bug where the I18n api was not supporting arrays as the translation replacement values.
+    Fixed bug where chrome would display "The given range isn't in document." errors for invalid ranges passed to setRng.
+    Fixed bug where the compat3x plugin wasn't working since the global tinymce references wasn't resolved correctly.
+    Fixed bug where the preview plugin wasn't encoding the base url passed into the iframe contents producing a xss bug.
+    Fixed bug where the dom parser/serializer wasn't handling some special elements like noframes, title and xmp.
+    Fixed bug where the dom parser/serializer wasn't handling cdata sections with comments inside.
+    Fixed bug where the editor would scroll to the top of the editable area if a dialog was closed in inline mode.
+    Fixed bug where the link dialog would not display the right rel value if rel_list was configured.
+    Fixed bug where the context menu would select images on some platforms but not others.
+    Fixed bug where the filenames of images were not retained on dragged and drop into the editor from the desktop.
+    Fixed bug where the paste plugin would misrepresent newlines when pasting plain text and having forced_root_block configured.
+    Fixed so that the error messages for the imagetools plugin is more human readable.
+    Fixed so the internal validate setting for the parser/serializer can't be set from editor initialization settings.
+Version 4.6.3 (2017-05-30)
+    Fixed bug where the arrow keys didn't work correctly when navigating on nested inline boundary elements.
+    Fixed bug where delete/backspace didn't work correctly on nested inline boundary elements.
+    Fixed bug where image editing didn't work on subsequent edits of the same image.
+    Fixed bug where charmap descriptions wouldn't properly wrap if they exceeded the width of the box.
+    Fixed bug where the default image upload handler only accepted 200 as a valid http status code.
+    Fixed so rel on target=_blank links gets forced with only noopener instead of both noopener and noreferrer.
+Version 4.6.2 (2017-05-23)
+    Fixed bug where the SaxParser would run out of memory on very large documents.
+    Fixed bug with formatting like font size wasn't applied to del elements.
+    Fixed bug where various api calls would be throwing exceptions if they where invoked on a removed editor instance.
+    Fixed bug where the branding position would be incorrect if the editor was inside a hidden tab and then later showed.
+    Fixed bug where the color levels feature in the imagetools dialog wasn't working properly.
+    Fixed bug where imagetools dialog wouldn't pre-load images from CORS domains, before trying to prepare them for editing.
+    Fixed bug where the tab key would move the caret to the next table cell if being pressed inside a list inside a table.
+    Fixed bug where the cut/copy operations would loose parent context like the current format etc.
+    Fixed bug with format preview not working on invalid elements excluded by valid_elements.
+    Fixed bug where blocks would be merged in incorrect order on backspace/delete.
+    Fixed bug where zero length text nodes would cause issues with the undo logic if there where iframes present.
+    Fixed bug where the font size/family select lists would throw errors if the first node was a comment.
+    Fixed bug with csp having to allow local script evaluation since it was used to detect global scope.
+    Fixed bug where CSP required a relaxed option for javascript: URLs in unsupported legacy browsers.
+    Fixed bug where a fake caret would be rendered for td with the contenteditable=false.
+    Fixed bug where typing would be blocked on IE 11 when within a nested contenteditable=true/false structure.
+Version 4.6.1 (2017-05-10)
+    Added configuration option to list plugin to disable tab indentation.
+    Fixed bug where format change on very specific content could cause the selection to change.
+    Fixed bug where TinyMCE could not be lazyloaded through jquery integration.
+    Fixed bug where entities in style attributes weren't decoded correctly on paste in webkit.
+    Fixed bug where fontsize_formats option had been renamed incorrectly.
+    Fixed bug with broken backspace/delete behaviour between contenteditable=false blocks.
+    Fixed bug where it wasn't possible to backspace to the previous line with the inline boundaries functionality turned on.
+    Fixed bug where is wasn't possible to move caret left and right around a linked image with the inline boundaries functionality turned on.
+    Fixed bug where pressing enter after/before hr element threw exception. Patch contributed bradleyke.
+    Fixed so the CSS in the visualblocks plugin doesn't overwrite background color. Patch contributed by Christian Rank.
+    Fixed bug where multibyte characters weren't encoded correctly. Patch contributed by James Tarkenton.
+    Fixed bug where shift-click to select within contenteditable=true fields wasn't working.
+Version 4.6.0 (2017-05-04)
+    Dropped support for IE 8-10 due to market share and lack of support from Microsoft. See tinymce docs for details.
+    Added an inline boundary caret position feature that makes it easier to type at the beginning/end of links/code elements.
+    Added a help plugin that adds a button and a dialog showing the editor shortcuts and loaded plugins.
+    Added an inline_boundaries option that allows you to disable the inline boundary feature if it's not desired.
+    Added a new ScrollIntoView event that allows you to override the default scroll to element behavior.
+    Added role and aria- attributes as valid elements in the default valid elements config.
+    Added new internal flag for PastePreProcess/PastePostProcess this is useful to know if the paste was coming from an external source.
+    Added new ignore function to UndoManager this works similar to transact except that it doesn't add an undo level by default.
+    Fixed so that urls gets retained for images when being edited. This url is then passed on to the upload handler.
+    Fixed so that the editors would be initialized on readyState interactive instead of complete.
+    Fixed so that the init event of the editor gets fired once all contentCSS files have been properly loaded.
+    Fixed so that width/height of the editor gets taken from the textarea element if it's explicitly specified in styles.
+    Fixed so that keep_styles set to false no longer clones class/style from the previous paragraph on enter.
+    Fixed so that the default line-height is 1.2em to avoid zwnbsp characters from producing text rendering glitches on Windows.
+    Fixed so that loading errors of content css gets presented by a notification message.
+    Fixed so figure image elements can be linked when selected this wraps the figure image in a anchor element.
+    Fixed bug where it wasn't possible to copy/paste rows with colspans by using the table copy/paste feature.
+    Fixed bug where the protect setting wasn't properly applied to header/footer parts when using the fullpage plugin.
+    Fixed bug where custom formats that specified upper case element names where not applied correctly.
+    Fixed bug where some screen readers weren't reading buttons due to an aria specific fix for IE 8.
+    Fixed bug where cut wasn't working correctly on iOS due to it's clipboard API not working correctly.
+    Fixed bug where Edge would paste div elements instead of paragraphs when pasting plain text.
+    Fixed bug where the textpattern plugin wasn't dealing with trailing punctuations correctly.
+    Fixed bug where image editing would some times change the image format from jpg to png.
+    Fixed bug where some UI elements could be inserted into the toolbar even if they where not registered.
+    Fixed bug where it was possible to click the TD instead of the character in the character map and that caused an exception.
+    Fixed bug where the font size/font family dropdowns would sometimes show an incorrect value due to css not being loaded in time.
+    Fixed bug with the media plugin inserting undefined instead of retaining size when media_dimensions was set to false.
+    Fixed bug with deleting images when forced_root_blocks where set to false.
+    Fixed bug where input focus wasn't properly handled on nested content editable elements.
+    Fixed bug where Chrome/Firefox would throw an exception when selecting images due to recent change of setBaseAndExtent support.
+    Fixed bug where malformed blobs would throw exceptions now they are simply ignored.
+    Fixed bug where backspace/delete wouldn't work properly in some cases where all contents was selected in WebKit.
+    Fixed bug with Angular producing errors since it was expecting events objects to be patched with their custom properties.
+    Fixed bug where the formatter would apply formatting to spellchecker errors now all bogus elements are excluded.
+    Fixed bug with backspace/delete inside table caption elements wouldn't behave properly on IE 11.
+    Fixed bug where typing after a contenteditable false inline element could move the caret to the end of that element.
+    Fixed bug where backspace before/after contenteditable false blocks wouldn't properly remove the right element.
+    Fixed bug where backspace before/after contenteditable false inline elements wouldn't properly empty the current block element.
+    Fixed bug where vertical caret navigation with a custom line-height would sometimes match incorrect positions.
+    Fixed bug with paste on Edge where character encoding wasn't handled properly due to a browser bug.
+    Fixed bug with paste on Edge where extra fragment data was inserted into the contents when pasting.
+    Fixed bug with pasting contents when having a whole block element selected on WebKit could cause WebKit spans to appear.
+    Fixed bug where the visualchars plugin wasn't working correctly showing invisible nbsp characters.
+    Fixed bug where browsers would hang if you tried to load some malformed html contents.
+    Fixed bug where the init call promise wouldn't resolve if the specified selector didn't find any matching elements.
+    Fixed bug where the Schema isValidChild function was case sensitive.
+Version 4.5.3 (2017-02-01)
+    Added keyboard navigation for menu buttons when the menu is in focus.
+    Added api to the list plugin for setting custom classes/attributes on lists.
+    Added validation for the anchor plugin input field according to W3C id naming specifications.
+    Fixed bug where media placeholders were removed after resize with the forced_root_block setting set to false.
+    Fixed bug where deleting selections with similar sibling nodes sometimes deleted the whole document.
+    Fixed bug with inlite theme where several toolbars would appear scrolling when more than one instance of the editor was in use.
+    Fixed bug where the editor would throw error with the fontselect plugin on hidden editor instances in Firefox.
+    Fixed bug where the background color would not stretch to the font size.
+    Fixed bug where font size would be removed when changing background color.
+    Fixed bug where the undomanager trimmed away whitespace between nodes on undo/redo.
+    Fixed bug where media_dimensions=false in media plugin caused the editor to throw an error.
+    Fixed bug where IE was producing font/u elements within links on paste.
+    Fixed bug where some button tooltips were broken when compat3x was in use.
+    Fixed bug where backspace/delete/typeover would remove the caption element.
+    Fixed bug where powerspell failed to function when compat3x was enabled.
+    Fixed bug where it wasn't possible to apply sub/sup on text with large font size.
+    Fixed bug where pre tags with spaces weren't treated as content.
+    Fixed bug where Meta+A would select the entire document instead of all contents in nested ce=true elements.
+Version 4.5.2 (2017-01-04)
+    Added missing keyboard shortcut description for the underline menu item in the format menu.
+    Fixed bug where external blob urls wasn't properly handled by editor upload logic. Patch contributed by David Oviedo.
+    Fixed bug where urls wasn't treated as a single word by the wordcount plugin.
+    Fixed bug where nbsp characters wasn't treated as word delimiters by the wordcount plugin.
+    Fixed bug where editor instance wasn't properly passed to the format preview logic. Patch contributed by NullQuery.
+    Fixed bug where the fake caret wasn't hidden when you moved selection to a cE=false element.
+    Fixed bug where it wasn't possible to edit existing code sample blocks.
+    Fixed bug where it wasn't possible to delete editor contents if the selection included an empty block.
+    Fixed bug where the formatter wasn't expanding words on some international characters. Patch contributed by Martin Larochelle.
+    Fixed bug where the open link feature wasn't working correctly on IE 11.
+    Fixed bug where enter before/after a cE=false block wouldn't properly padd the paragraph with an br element.
+    Fixed so font size and font family select boxes always displays a value by using the runtime style as a fallback.
+    Fixed so missing plugins will be logged to console as warnings rather than halting the initialization of the editor.
+    Fixed so splitbuttons become normal buttons in advlist plugin if styles are empty. Patch contributed by René Schleusner.
+    Fixed so you can multi insert rows/cols by selecting table cells and using insert rows/columns.
+Version 4.5.1 (2016-12-07)
+    Fixed bug where the lists plugin wouldn't initialize without the advlist plugins if served from cdn.
+    Fixed bug where selectors with "*" would cause the style format preview to throw an error.
+    Fixed bug with toggling lists off on lists with empty list items would throw an error.
+    Fixed bug where editing images would produce non existing blob uris.
+    Fixed bug where the offscreen toc selection would be treated as the real toc element.
+    Fixed bug where the aria level attribute for element path would have an incorrect start index.
+    Fixed bug where the offscreen selection of cE=false that where very wide would be shown onscreen. Patch contributed by Steven Bufton.
+    Fixed so the default_link_target gets applied to links created by the autolink plugin.
+    Fixed so that the name attribute gets removed by the anchor plugin if editing anchors.
+Version 4.5.0 (2016-11-23)
+    Added new toc plugin allows you to insert table of contents based on editor headings.
+    Added new auto complete menu to all url fields. Adds history, link to anchors etc.
+    Added new sidebar api that allows you to add custom sidebar panels and buttons to toggle these.
+    Added new insert menu button that allows you to have multiple insert functions under the same menu button.
+    Added new open link feature to ctrl+click, alt+enter and context menu.
+    Added new media_embed_handler option to allow the media plugin to be populated with custom embeds.
+    Added new support for editing transparent images using the image tools dialog.
+    Added new images_reuse_filename option to allow filenames of images to be retained for upload.
+    Added new security feature where links with target="_blank" will by default get rel="noopener noreferrer".
+    Added new allow_unsafe_link_target to allow you to opt-out of the target="_blank" security feature.
+    Added new style_formats_autohide option to automatically hide styles based on context.
+    Added new codesample_content_css option to specify where the code sample prism css is loaded from.
+    Added new support for Japanese/Chinese word count following the unicode standards on this.
+    Added new fragmented undo levels this dramatically reduces flicker on contents with iframes.
+    Added new live previews for complex elements like table or lists.
+    Fixed bug where it wasn't possible to properly tab between controls in a dialog with a disabled form item control.
+    Fixed bug where firefox would generate a rectangle on elements produced after/before a cE=false elements.
+    Fixed bug with advlist plugin not switching list element format properly in some edge cases.
+    Fixed bug where col/rowspans wasn't correctly computed by the table plugin in some cases.
+    Fixed bug where the table plugin would thrown an error if object_resizing was disabled.
+    Fixed bug where some invalid markup would cause issues when running in XHTML mode. Patch contributed by Charles Bourasseau.
+    Fixed bug where the fullscreen class wouldn't be removed properly when closing dialogs.
+    Fixed bug where the PastePlainTextToggle event wasn't fired by the paste plugin when the state changed.
+    Fixed bug where table the row type wasn't properly updated in table row dialog. Patch contributed by Matthias Balmer.
+    Fixed bug where select all and cut wouldn't place caret focus back to the editor in WebKit. Patch contributed by Daniel Jalkut.
+    Fixed bug where applying cell/row properties to multiple cells/rows would reset other unchanged properties.
+    Fixed bug where some elements in the schema would have redundant/incorrect children.
+    Fixed bug where selector and target options would cause issues if used together.
+    Fixed bug where drag/drop of images from desktop on chrome would thrown an error.
+    Fixed bug where cut on WebKit/Blink wouldn't add an undo level.
+    Fixed bug where IE 11 would scroll to the cE=false elements when they where selected.
+    Fixed bug where keys like F5 wouldn't work when a cE=false element was selected.
+    Fixed bug where the undo manager wouldn't stop the typing state when commands where executed.
+    Fixed bug where unlink on wrapped links wouldn't work properly.
+    Fixed bug with drag/drop of images on WebKit where the image would be deleted form the source editor.
+    Fixed bug where the visual characters mode would be disabled when contents was extracted from the editor.
+    Fixed bug where some browsers would toggle of formats applied to the caret when clicking in the editor toolbar.
+    Fixed bug where the custom theme function wasn't working correctly.
+    Fixed bug where image option for custom buttons required you to have icon specified as well.
+    Fixed bug where the context menu and contextual toolbars would be visible at the same time and sometimes overlapping.
+    Fixed bug where the noneditable plugin would double wrap elements when using the noneditable_regexp option.
+    Fixed bug where tables would get padding instead of margin when you used the indent button.
+    Fixed bug where the charmap plugin wouldn't properly insert non breaking spaces.
+    Fixed bug where the color previews in color input boxes wasn't properly updated.
+    Fixed bug where the list items of previous lists wasn't merged in the right order.
+    Fixed bug where it wasn't possible to drag/drop inline-block cE=false elements on IE 11.
+    Fixed bug where some table cell merges would produce incorrect rowspan/colspan.
+    Fixed so the font size of the editor defaults to 14px instead of 11px this can be overridden by custom css.
+    Fixed so wordcount is debounced to reduce cpu hogging on larger texts.
+    Fixed so tinymce global gets properly exported as a module when used with some module bundlers.
+    Fixed so it's possible to specify what css properties you want to preview on specific formats.
+    Fixed so anchors are contentEditable=false while within the editor.
+    Fixed so selected contents gets wrapped in a inline code element by the codesample plugin.
+    Fixed so conditional comments gets properly stripped independent of case. Patch contributed by Georgii Dolzhykov.
+    Fixed so some escaped css sequences gets properly handled. Patch contributed by Georgii Dolzhykov.
+    Fixed so notifications with the same message doesn't get displayed at the same time.
+    Fixed so F10 can be used as an alternative key to focus to the toolbar.
+    Fixed various api documentation issues and typos.
+    Removed layer plugin since it wasn't really ported from 3.x and there doesn't seem to be much use for it.
+    Removed moxieplayer.swf from the media plugin since it wasn't used by the media plugin.
+    Removed format state from the advlist plugin to be more consistent with common word processors.
+Version 4.4.3 (2016-09-01)
+    Fixed bug where copy would produce an exception on Chrome.
+    Fixed bug where deleting lists on IE 11 would merge in correct text nodes.
+    Fixed bug where deleting partial lists with indentation wouldn't cause proper normalization.
+Version 4.4.2 (2016-08-25)
+    Added new importcss_exclusive option to disable unique selectors per group.
+    Added new group specific selector_converter option to importcss plugin.
+    Added new codesample_languages option to apply custom languages to codesample plugin.
+    Added new codesample_dialog_width/codesample_dialog_height options.
+    Fixed bug where fullscreen button had an incorrect keyboard shortcut.
+    Fixed bug where backspace/delete wouldn't work correctly from a block to a cE=false element.
+    Fixed bug where smartpaste wasn't detecting links with special characters in them like tilde.
+    Fixed bug where the editor wouldn't get proper focus if you clicked on a cE=false element.
+    Fixed bug where it wasn't possible to copy/paste table rows that had merged cells.
+    Fixed bug where merging cells could some times produce invalid col/rowspan attibute values.
+    Fixed bug where getBody would sometimes thrown an exception now it just returns null if the iframe is clobbered.
+    Fixed bug where drag/drop of cE=false element wasn't properly constrained to viewport.
+    Fixed bug where contextmenu on Mac would collapse any selection to a caret.
+    Fixed bug where rtl mode wasn't rendered properly when loading a language pack with the rtl flag.
+    Fixed bug where Kamer word bounderies would be stripped from contents.
+    Fixed bug where lists would sometimes render two dots or numbers on the same line.
+    Fixed bug where the skin_url wasn't used by the inlite theme.
+    Fixed so data attributes are ignored when comparing formats in the formatter.
+    Fixed so it's possible to disable inline toolbars in the inlite theme.
+    Fixed so template dialog gets resized if it doesn't fit the window viewport.
+Version 4.4.1 (2016-07-26)
+    Added smart_paste option to paste plugin to allow disabling the paste behavior if needed.
+    Fixed bug where png urls wasn't properly detected by the smart paste logic.
+    Fixed bug where the element path wasn't working properly when multiple editor instances where used.
+    Fixed bug with creating lists out of multiple paragraphs would just create one list item instead of multiple.
+    Fixed bug where scroll position wasn't properly handled by the inlite theme to place the toolbar properly.
+    Fixed bug where multiple instances of the editor using the inlite theme didn't render the toolbar properly.
+    Fixed bug where the shortcut label for fullscreen mode didn't match the actual shortcut key.
+    Fixed bug where it wasn't possible to select cE=false blocks using touch devices on for example iOS.
+    Fixed bug where it was possible to select the child image within a cE=false on IE 11.
+    Fixed so inserts of html containing lists doesn't merge with any existing lists unless it's a paste operation.
+Version 4.4.0 (2016-06-30)
+    Added new inlite theme this is a more lightweight inline UI.
+    Added smarter paste logic that auto detects urls in the clipboard and inserts images/links based on that.
+    Added a better image resize algorithm for better image quality in the imagetools plugin.
+    Fixed bug where it wasn't possible to drag/dropping cE=false elements on FF.
+    Fixed bug where backspace/delete before/after a cE=false block would produce a new paragraph.
+    Fixed bug where list style type css property wasn't preserved when indenting lists.
+    Fixed bug where merging of lists where done even if the list style type was different.
+    Fixed bug where the image_dataimg_filter function wasn't used when pasting images.
+    Fixed bug where nested editable within a non editable element would cause scroll on focus in Chrome.
+    Fixed so invalid targets for inline mode is blocked on initialization. We only support elements that can have children.
+Version 4.3.13 (2016-06-08)
+    Added characters with a diacritical mark to charmap plugin. Patch contributed by Dominik Schilling.
+    Added better error handling if the image proxy service would produce errors.
+    Fixed issue with pasting list items into list items would produce nested list rather than a merged list.
+    Fixed bug where table selection could get stuck in selection mode for inline editors.
+    Fixed bug where it was possible to place the caret inside the resize grid elements.
+    Fixed bug where it wasn't possible to place in elements horizontally adjacent cE=false blocks.
+    Fixed bug where multiple notifications wouldn't be properly placed on screen.
+    Fixed bug where multiple editor instance of the same id could be produces in some specific integrations.
+Version 4.3.12 (2016-05-10)
+    Fixed bug where focus calls couldn't be made inside the editors PostRender event handler.
+    Fixed bug where some translations wouldn't work as expected due to a bug in editor.translate.
+    Fixed bug where the node change event could fire with a node out side the root of the editor.
+    Fixed bug where Chrome wouldn't properly present the keyboard paste clipboard details when paste was clicked.
+    Fixed bug where merged cells in tables couldn't be selected from right to left.
+    Fixed bug where insert row wouldn't properly update a merged cells rowspan property.
+    Fixed bug where the color input boxes preview field wasn't properly set on initialization.
+    Fixed bug where IME composition inside table cells wouldn't work as expected on IE 11.
+    Fixed so all shadow dom support is under and experimental flag due to flaky browser support.
+Version 4.3.11 (2016-04-25)
+    Fixed bug where it wasn't possible to insert empty blocks though the API unless they where padded.
+    Fixed bug where you couldn't type the Euro character on Windows.
+    Fixed bug where backspace/delete from a cE=false element to a text block didn't work properly.
+    Fixed bug where the text color default grid would render incorrectly.
+    Fixed bug where the codesample plugin wouldn't load the css in the editor for multiple editors.
+    Fixed so the codesample plugin textarea gets focused by default.
+Version 4.3.10 (2016-04-12)
+    Fixed bug where the key "y" on WebKit couldn't be entered due to conflict with keycode for F10 on keypress.
+Version 4.3.9 (2016-04-12)
+    Added support for focusing the contextual toolbars using keyboard.
+    Added keyboard support for slider UI controls. You can no increase/decrease using arrow keys.
+    Added url pattern matching for Dailymotion to media plugin. Patch contributed by Bertrand Darbon.
+    Added body_class to template plugin preview. Patch contributed by Milen Petrinski.
+    Added options to better override textcolor pickers with custom colors. Patch contributed by Xavier Boubert.
+    Added visual arrows to inline contextual toolbars so that they point to the element being active.
+    Fixed so toolbars for tables or other larger elements get better positioned below the scrollable viewport.
+    Fixed bug where it was possible to click links inside cE=false blocks.
+    Fixed bug where event targets wasn't properly handled in Safari Technical Preview.
+    Fixed bug where drag/drop text in FF 45 would make the editor caret invisible.
+    Fixed bug where the remove state wasn't properly set on editor instances when detected as clobbered.
+    Fixed bug where offscreen selection of some cE=false elements would render onscreen. Patch contributed by Steven Bufton
+    Fixed bug where enter would clone styles out side the root on editors inside a span. Patch contributed by ChristophKaser.
+    Fixed bug where drag/drop of images into the editor didn't work correctly in FF.
+    Fixed so the first item in panels for the imagetools dialog gets proper keyboard focus.
+    Changed the Meta+Shift+F shortcut to Ctrl+Shift+F since Czech, Slovak, Polish languages used the first one for input.
+Version 4.3.8 (2016-03-15)
+    Fixed bug where inserting HR at the end of a block element would produce an extra empty block.
+    Fixed bug where links would be clickable when readonly mode was enabled.
+    Fixed bug where the formatter would normalize to the wrong node on very specific content.
+    Fixed bug where some nested list items couldn't be indented properly.
+    Fixed bug where links where clickable in the preview dialog.
+    Fixed so the alt attribute doesn't get padded with an empty value by default.
+    Fixed so nested alignment works more correctly. You will now alter the alignment to the closest block parent.
+Version 4.3.7 (2016-03-02)
+    Fixed bug where incorrect icons would be rendered for imagetools edit and color levels.
+    Fixed bug where navigation using arrow keys inside a SelectBox didn't move up/down.
+    Fixed bug where the visualblocks plugin would render borders round internal UI elements.
+Version 4.3.6 (2016-03-01)
+    Added new paste_remember_plaintext_info option to allow a global disable of the plain text mode notification.
+    Added new PastePlainTextToggle event that fires when plain text mode toggles on/off.
+    Fixed bug where it wasn't possible to select media elements since the drag logic would snap it to mouse cursor.
+    Fixed bug where it was hard to place the caret inside nested cE=true elements when the outer cE=false element was focused.
+    Fixed bug where editors wouldn't properly initialize if both selector and mode where used.
+    Fixed bug where IME input inside table cells would switch the IME off.
+    Fixed bug where selection inside the first table cell would cause the whole table cell to get selected.
+    Fixed bug where error handling of images being uploaded wouldn't properly handle faulty statuses.
+    Fixed bug where inserting contents before a HR would cause an exception to be thrown.
+    Fixed bug where copy/paste of Excel data would be inserted as an image.
+    Fixed caret position issues with copy/paste of inline block cE=false elements.
+    Fixed issues with various menu item focus bugs in Chrome. Where the focused menu bar item wasn't properly blurred.
+    Fixed so the notifications have a solid background since it would be hard to read if there where text under it.
+    Fixed so notifications gets animated similar to the ones used by dialogs.
+    Fixed so larger images that gets pasted is handled better.
+    Fixed so the window close button is more uniform on various platform and also increased it's hit area.
+Version 4.3.5 (2016-02-11)
+    Npm version bump due to package not being fully updated.
+Version 4.3.4 (2016-02-11)
+    Added new OpenWindow/CloseWindow events that gets fired when windows open/close.
+    Added new NewCell/NewRow events that gets fired when table cells/rows are created.
+    Added new Promise return value to tinymce.init makes it easier to handle initialization.
+    Removed the jQuery version the jQuery plugin is now moved into the main package.
+    Removed jscs from build process since eslint can now handle code style checking.
+    Fixed various bugs with drag/drop of contentEditable:false elements.
+    Fixed bug where deleting of very specific nested list items would result in an odd list.
+    Fixed bug where lists would get merged with adjacent lists outside the editable inline root.
+    Fixed bug where MS Edge would crash when closing a dialog then clicking a menu item.
+    Fixed bug where table cell selection would add undo levels.
+    Fixed bug where table cell selection wasn't removed when inline editor where removed.
+    Fixed bug where table cell selection wouldn't work properly on nested tables.
+    Fixed bug where table merge menu would be available when merging between thead and tbody.
+    Fixed bug where table row/column resize wouldn't get properly removed when the editor was removed.
+    Fixed bug where Chrome would scroll to the editor if there where a empty hash value in document url.
+    Fixed bug where the cache suffix wouldn't work correctly with the importcss plugin.
+    Fixed bug where selection wouldn't work properly on MS Edge on Windows Phone 10.
+    Fixed so adjacent pre blocks gets joined into one pre block since that seems like the user intent.
+    Fixed so events gets properly dispatched in shadow dom. Patch provided by Nazar Mokrynskyi.
+Version 4.3.3 (2016-01-14)
+    Added new table_resize_bars configuration setting.  This setting allows you to disable the table resize bars.
+    Added new beforeInitialize event to tinymce.util.XHR lets you modify XHR properties before open. Patch contributed by Brent Clintel.
+    Added new autolink_pattern setting to autolink plugin. Enables you to override the default autolink formats. Patch contributed by Ben Tiedt.
+    Added new charmap option that lets you override the default charmap of the charmap plugin.
+    Added new charmap_append option that lets you add new characters to the default charmap of the charmap plugin.
+    Added new insertCustomChar event that gets fired when a character is inserted by the charmap plugin.
+    Fixed bug where table cells started with a superfluous &nbsp; in IE10+.
+    Fixed bug where table plugin would retain all BR tags when cells were merged.
+    Fixed bug where media plugin would strip underscores from youtube urls.
+    Fixed bug where IME input would fail on IE 11 if you typed within a table.
+    Fixed bug where double click selection of a word would remove the space before the word on insert contents.
+    Fixed bug where table plugin would produce exceptions when hovering tables with invalid structure.
+    Fixed bug where fullscreen wouldn't scroll back to it's original position when untoggled.
+    Fixed so the template plugins templates setting can be a function that gets a callback that can provide templates.
+Version 4.3.2 (2015-12-14)
+    Fixed bug where the resize bars for table cells were not affected by the object_resizing property.
+    Fixed bug where the contextual table toolbar would appear incorrectly if TinyMCE was initialized inline inside a table.
+    Fixed bug where resizing table cells did not fire a node change event or add an undo level.
+    Fixed bug where double click selection of text on IE 11 wouldn't work properly.
+    Fixed bug where codesample plugin would incorrectly produce br elements inside code elements.
+    Fixed bug where media plugin would strip dashes from youtube urls.
+    Fixed bug where it was possible to move the caret into the table resize bars.
+    Fixed bug where drag/drop into a cE=false element was possible on IE.
+Version 4.3.1 (2015-11-30)
+    Fixed so it's possible to disable the table inline toolbar by setting it to false or an empty string.
+    Fixed bug where it wasn't possible to resize some tables using the drag handles.
+    Fixed bug where unique id:s would clash for multiple editor instances and cE=false selections.
+    Fixed bug where the same plugin could be initialized multiple times.
+    Fixed bug where the table inline toolbars would be displayed at the same time as the image toolbars.
+    Fixed bug where the table selection rect wouldn't be removed when selecting another control element.
+Version 4.3.0 (2015-11-23)
+    Added new table column/row resize support. Makes it a lot more easy to resize the columns/rows in a table.
+    Added new table inline toolbar. Makes it easier to for example add new rows or columns to a table.
+    Added new notification API. Lets you display floating notifications to the end user.
+    Added new codesample plugin that lets you insert syntax highlighted pre elements into the editor.
+    Added new image_caption to images. Lets you create images with captions using a HTML5 figure/figcaption elements.
+    Added new live previews of embeded videos. Lets you play the video right inside the editor.
+    Added new setDirty method and "dirty" event to the editor. Makes it easier to track the dirty state change.
+    Added new setMode method to Editor instances that lets you dynamically switch between design/readonly.
+    Added new core support for contentEditable=false elements within the editor overrides the browsers broken behavior.
+    Rewrote the noneditable plugin to use the new contentEditable false core logic.
+    Fixed so the dirty state doesn't set to false automatically when the undo index is set to 0.
+    Fixed the Selection.placeCaretAt so it works better on IE when the coordinate is between paragraphs.
+    Fixed bug where data-mce-bogus="all" element contents where counted by the word count plugin.
+    Fixed bug where contentEditable=false elements would be indented by the indent buttons.
+    Fixed bug where images within contentEditable=false would be selected in WebKit on mouse click.
+    Fixed bug in DOMUntils split method where the replacement parameter wouldn't work on specific cases.
+    Fixed bug where the importcss plugin would import classes from the skin content css file.
+    Fixed so all button variants have a wrapping span for it's text to make it easier to skin.
+    Fixed so it's easier to exit pre block using the arrow keys.
+    Fixed bug where listboxes with fix widths didn't render correctly.
+Version 4.2.8 (2015-11-13)
+    Fixed bug where it was possible to delete tables as the inline root element if all columns where selected.
+    Fixed bug where the UI buttons active state wasn't properly updated due to recent refactoring of that logic.
+Version 4.2.7 (2015-10-27)
+    Fixed bug where backspace/delete would remove all formats on the last paragraph character in WebKit/Blink.
+    Fixed bug where backspace within a inline format element with a bogus caret container would move the caret.
+    Fixed bug where backspace/delete on selected table cells wouldn't add an undo level.
+    Fixed bug where script tags embedded within the editor could sometimes get a mce- prefix prepended to them
+    Fixed bug where validate: false option could produce an error to be thrown from the Serialization step.
+    Fixed bug where inline editing of a table as the root element could let the user delete that table.
+    Fixed bug where inline editing of a table as the root element wouldn't properly handle enter key.
+    Fixed bug where inline editing of a table as the root element would normalize the selection incorrectly.
+    Fixed bug where inline editing of a list as the root element could let the user delete that list.
+    Fixed bug where inline editing of a list as the root element could let the user split that list.
+    Fixed bug where resize handles would be rendered on editable root elements such as table.
+Version 4.2.6 (2015-09-28)
+    Added capability to set request headers when using XHRs.
+    Added capability to upload local images automatically default delay is set to 30 seconds after editing images.
+    Added commands ids mceEditImage, mceAchor and mceMedia to be avaiable from execCommand.
+    Added Edge browser to saucelabs grunt task. Patch contributed by John-David Dalton.
+    Fixed bug where blob uris not produced by tinymce would produce HTML invalid markup.
+    Fixed bug where selection of contents of a nearly empty editor in Edge would sometimes fail.
+    Fixed bug where color styles woudln't be retained on copy/paste in Blink/Webkit.
+    Fixed bug where the table plugin would throw an error when inserting rows after a child table.
+    Fixed bug where the template plugin wouldn't handle functions as variable replacements.
+    Fixed bug where undo/redo sometimes wouldn't work properly when applying formatting collapsed ranges.
+    Fixed bug where shift+delete wouldn't do a cut operation on Blink/WebKit.
+    Fixed bug where cut action wouldn't properly store the before selection bookmark for the undo level.
+    Fixed bug where backspace in side an empty list element on IE would loose editor focus.
+    Fixed bug where the save plugin wouldn't enable the buttons when a change occurred.
+    Fixed bug where Edge wouldn't initialize the editor if a document.domain was specified.
+    Fixed bug where enter key before nested images would sometimes not properly expand the previous block.
+    Fixed bug where the inline toolbars wouldn't get properly hidden when blurring the editor instance.
+    Fixed bug where Edge would paste Chinese characters on some Windows 10 installations.
+    Fixed bug where IME would loose focus on IE 11 due to the double trailing br bug fix.
+    Fixed bug where the proxy url in imagetools was incorrect. Patch contributed by Wong Ho Wang.
+Version 4.2.5 (2015-08-31)
+    Added fullscreen capability to embedded youtube and vimeo videos.
+    Fixed bug where the uploadImages call didn't work on IE 10.
+    Fixed bug where image place holders would be uploaded by uploadImages call.
+    Fixed bug where images marked with bogus would be uploaded by the uploadImages call.
+    Fixed bug where multiple calls to uploadImages would result in decreased performance.
+    Fixed bug where pagebreaks were editable to imagetools patch contributed by Rasmus Wallin.
+    Fixed bug where the element path could cause too much recursion exception.
+    Fixed bug for domains containing ".min". Patch contributed by Loïc Février.
+    Fixed so validation of external links to accept a number after www. Patch contributed by Victor Carvalho.
+    Fixed so the charmap is exposed though execCommand. Patch contributed by Matthew Will.
+    Fixed so that the image uploads are concurrent for improved performance.
+    Fixed various grammar problems in inline documentation. Patches provided by nikolas.
+Version 4.2.4 (2015-08-17)
+    Added picture as a valid element to the HTML 5 schema. Patch contributed by Adam Taylor.
+    Fixed bug where contents would be duplicated on drag/drop within the same editor.
+    Fixed bug where floating/alignment of images on Edge wouldn't work properly.
+    Fixed bug where it wasn't possible to drag images on IE 11.
+    Fixed bug where image selection on Edge would sometimes fail.
+    Fixed bug where contextual toolbars icons wasn't rendered properly when using the toolbar_items_size.
+    Fixed bug where searchreplace dialog doesn't get prefilled with the selected text.
+    Fixed bug where fragmented matches wouldn't get properly replaced by the searchreplace plugin.
+    Fixed bug where enter key wouldn't place the caret if was after a trailing space within an inline element.
+    Fixed bug where the autolink plugin could produce multiple links for the same text on Gecko.
+    Fixed bug where EditorUpload could sometimes throw an exception if the blob wasn't found.
+    Fixed xss issues with media plugin not properly filtering out some script attributes.
+Version 4.2.3 (2015-07-30)
+    Fixed bug where image selection wasn't possible on Edge due to incompatible setBaseAndExtend API.
+    Fixed bug where image blobs urls where not properly destroyed by the imagetools plugin.
+    Fixed bug where keyboard shortcuts wasn't working correctly on IE 8.
+    Fixed skin issue where the borders of panels where not visible on IE 8.
+Version 4.2.2 (2015-07-22)
+    Fixed bug where float panels were not being hidden on inline editor blur when fixed_toolbar_container config option was in use.
+    Fixed bug where combobox states wasn't properly updated if contents where updated without keyboard.
+    Fixed bug where pasting into textbox or combobox would move the caret to the end of text.
+    Fixed bug where removal of bogus span elements before block elements would remove whitespace between nodes.
+    Fixed bug where repositioning of inline toolbars where async and producing errors if the editor was removed from DOM to early. Patch by iseulde.
+    Fixed bug where element path wasn't working correctly. Patch contributed by iseulde.
+    Fixed bug where menus wasn't rendered correctly when custom images where added to a menu. Patch contributed by Naim Hammadi.
+Version 4.2.1 (2015-06-29)
+    Fixed bug where back/forward buttons in the browser would render blob images as broken images.
+    Fixed bug where Firefox would throw regexp to big error when replacing huge base64 chunks.
+    Fixed bug rendering issues with resize and context toolbars not being placed properly until next animation frame.
+    Fixed bug where the rendering of the image while cropping would some times not be centered correctly.
+    Fixed bug where listbox items with submenus would me selected as active.
+    Fixed bug where context menu where throwing an error when rendering.
+    Fixed bug where resize both option wasn't working due to resent addClass API change. Patch contributed by Jogai.
+    Fixed bug where a hideAll call for container rendered inline toolbars would throw an error.
+    Fixed bug where onclick event handler on combobox could cause issues if element.id was a function by some polluting libraries.
+    Fixed bug where listboxes wouldn't get proper selected sub menu item when using link_list or image_list.
+    Fixed so the UI controls are as wide as 4.1.x to avoid wrapping controls in toolbars.
+    Fixed so the imagetools dialog is adaptive for smaller screen sizes.
+Version 4.2.0 (2015-06-25)
+    Added new flat default skin to make the UI more modern.
+    Added new imagetools plugin, lets you crop/resize and apply filters to images.
+    Added new contextual toolbars support to the API lets you add floating toolbars for specific CSS selectors.
+    Added new promise feature fill as tinymce.util.Promise.
+    Added new built in image upload feature lets you upload any base64 encoded image within the editor as files.
+    Fixed bug where resize handles would appear in the right position in the wrong editor when switching between resizable content in different inline editors.
+    Fixed bug where tables would not be inserted in inline mode due to previous float panel fix.
+    Fixed bug where floating panels would remain open when focus was lost on inline editors.
+    Fixed bug where cut command on Chrome would thrown a browser security exception.
+    Fixed bug where IE 11 sometimes would report an incorrect size for images in the image dialog.
+    Fixed bug where it wasn't possible to remove inline formatting at the end of block elements.
+    Fixed bug where it wasn't possible to delete table cell contents when cell selection was vertical.
+    Fixed bug where table cell wasn't emptied from block elements if delete/backspace where pressed in empty cell.
+    Fixed bug where cmd+shift+arrow didn't work correctly on Firefox mac when selecting to start/end of line.
+    Fixed bug where removal of bogus elements would sometimes remove whitespace between nodes.
+    Fixed bug where the resize handles wasn't updated when the main window was resized.
+    Fixed so script elements gets removed by default to prevent possible XSS issues in default config implementations.
+    Fixed so the UI doesn't need manual reflows when using non native layout managers.
+    Fixed so base64 encoded images doesn't slow down the editor on modern browsers while editing.
+    Fixed so all UI elements uses touch events to improve mobile device support.
+    Removed the touch click quirks patch for iOS since it did more harm than good.
+    Removed the non proportional resize handles since. Unproportional resize can still be done by holding the shift key.
+Version 4.1.10 (2015-05-05)
+    Fixed bug where plugins loaded with compat3x would sometimes throw errors when loading using the jQuery version.
+    Fixed bug where extra empty paragraphs would get deleted in WebKit/Blink due to recent Quriks fix.
+    Fixed bug where the editor wouldn't work properly on IE 12 due to some required browser sniffing.
+    Fixed bug where formatting shortcut keys where interfering with Mac OS X screenshot keys.
+    Fixed bug where the caret wouldn't move to the next/previous line boundary on Cmd+Left/Right on Gecko.
+    Fixed bug where it wasn't possible to remove formats from very specific nested contents.
+    Fixed bug where undo levels wasn't produced when typing letters using the shift or alt+ctrl modifiers.
+    Fixed bug where the dirty state wasn't properly updated when typing using the shift or alt+ctrl modifiers.
+    Fixed bug where an error would be thrown if an autofocused editor was destroyed quickly after its initialization. Patch provided by thorn0.
+    Fixed issue with dirty state not being properly updated on redo operation.
+    Fixed issue with entity decoder not handling incorrectly written numeric entities.
+    Fixed issue where some PI element values wouldn't be properly encoded.
+Version 4.1.9 (2015-03-10)
+    Fixed bug where indentation wouldn't work properly for non list elements.
+    Fixed bug with image plugin not pulling the image dimensions out correctly if a custom document_base_url was used.
+    Fixed bug where ctrl+alt+[1-9] would conflict with the AltGr+[1-9] on Windows. New shortcuts is ctrl+shift+[1-9].
+    Fixed bug with removing formatting on nodes in inline mode would sometimes include nodes outside the editor body.
+    Fixed bug where extra nbsp:s would be inserted when you replaced a word surrounded by spaces using insertContent.
+    Fixed bug with pasting from Google Docs would produce extra strong elements and line feeds.
+Version 4.1.8 (2015-03-05)
+    Added new html5 sizes attribute to img elements used together with srcset.
+    Added new elementpath option that makes it possible to disable the element path but keep the statusbar.
+    Added new option table_style_by_css for the table plugin to set table styling with css rather than table attributes.
+    Added new link_assume_external_targets option to prompt the user to prepend http:// prefix if the supplied link does not contain a protocol prefix.
+    Added new image_prepend_url option to allow a custom base path/url to be added to images.
+    Added new table_appearance_options option to make it possible to disable some options.
+    Added new image_title option to make it possible to alter the title of the image, disabled by default.
+    Fixed bug where selection starting from out side of the body wouldn't produce a proper selection range on IE 11.
+    Fixed bug where pressing enter twice before a table moves the cursor in the table and causes a javascript error.
+    Fixed bug where advanced image styles were not respected.
+    Fixed bug where the less common Shift+Delete didn't produce a proper cut operation on WebKit browsers.
+    Fixed bug where image/media size constrain logic would produce NaN when handling non number values.
+    Fixed bug where internal classes where removed by the removeformat command.
+    Fixed bug with creating links table cell contents with a specific selection would throw a exceptions on WebKit/Blink.
+    Fixed bug where valid_classes option didn't work as expected according to docs. Patch provided by thorn0.
+    Fixed bug where jQuery plugin would patch the internal methods multiple times. Patch provided by Drew Martin.
+    Fixed bug where backspace key wouldn't delete the current selection of newly formatted content.
+    Fixed bug where type over of inline formatting elements wouldn't properly keep the format on WebKit/Blink.
+    Fixed bug where selection needed to be properly normalized on modern IE versions.
+    Fixed bug where Command+Backspace didn't properly delete the whole line of text but the previous word.
+    Fixed bug where UI active states wheren't properly updated on IE if you placed caret within the current range.
+    Fixed bug where delete/backspace on WebKit/Blink would remove span elements created by the user.
+    Fixed bug where delete/backspace would produce incorrect results when deleting between two text blocks with br elements.
+    Fixed bug where captions where removed when pasting from MS Office.
+    Fixed bug where lists plugin wouldn't properly remove fully selected nested lists.
+    Fixed bug where the ttf font used for icons would throw an warning message on Gecko on Mac OS X.
+    Fixed a bug where applying a color to text did not update the undo/redo history.
+    Fixed so shy entities gets displayed when using the visualchars plugin.
+    Fixed so removeformat removes ins/del by default since these might be used for strikethough.
+    Fixed so multiple language packs can be loaded and added to the global I18n data structure.
+    Fixed so transparent color selection gets treated as a normal color selection. Patch contributed by Alexander Hofbauer.
+    Fixed so it's possible to disable autoresize_overflow_padding, autoresize_bottom_margin options by setting them to false.
+    Fixed so the charmap plugin shows the description of the character in the dialog. Patch contributed by Jelle Hissink.
+    Removed address from the default list of block formats since it tends to be missused.
+    Fixed so the pre block format is called preformatted to make it more verbose.
+    Fixed so it's possible to context scope translation strings this isn't needed most of the time.
+    Fixed so the max length of the width/height input fields of the media dialog is 5 instead of 3.
+    Fixed so drag/dropped contents gets properly processed by paste plugin since it's basically a paste. Patch contributed by Greg Fairbanks.
+    Fixed so shortcut keys for headers is ctrl+alt+[1-9] instead of ctrl+[1-9] since these are for switching tabs in the browsers.
+    Fixed so "u" doesn't get converted into a span element by the legacy input filter. Since this is now a valid HTML5 element.
+    Fixed font families in order to provide appropriate web-safe fonts.
+Version 4.1.7 (2014-11-27)
+    Added HTML5 schema support for srcset, source and picture. Patch contributed by mattheu.
+    Added new cache_suffix setting to enable cache busting by producing unique urls.
+    Added new paste_convert_word_fake_lists option to enable users to disable the fake lists convert logic.
+    Fixed so advlist style changes adds undo levels for each change.
+    Fixed bug where WebKit would sometimes produce an exception when the autolink plugin where looking for URLs.
+    Fixed bug where IE 7 wouldn't be rendered properly due to aggressive css compression.
+    Fixed bug where DomQuery wouldn't accept window as constructor element.
+    Fixed bug where the color picker in 3.x dialogs wouldn't work properly. Patch contributed by Callidior.
+    Fixed bug where the image plugin wouldn't respect the document_base_url.
+    Fixed bug where the jQuery plugin would fail to append to elements named array prototype names.
+Version 4.1.6 (2014-10-08)
+    Fixed bug with clicking on the scrollbar of the iframe would cause a JS error to be thrown.
+    Fixed bug where null would produce an exception if you passed it to selection.setRng.
+    Fixed bug where Ctrl/Cmd+Tab would indent the current list item if you switched tabs in the browser.
+    Fixed bug where pasting empty cells from Excel would result in a broken table.
+    Fixed bug where it wasn't possible to switch back to default list style type.
+    Fixed issue where the select all quirk fix would fire for other modifiers than Ctrl/Cmd combinations.
+    Replaced jake with grunt since it is more mainstream and has better plugin support.
+Version 4.1.5 (2014-09-09)
+    Fixed bug where sometimes the resize rectangles wouldn't properly render on images on WebKit/Blink.
+    Fixed bug in list plugin where delete/backspace would merge empty LI elements in lists incorrectly.
+    Fixed bug where empty list elements would result in empty LI elements without it's parent container.
+    Fixed bug where backspace in empty caret formatted element could produce an type error exception of Gecko.
+    Fixed bug where lists pasted from word with a custom start index above 9 wouldn't be properly handled.
+    Fixed bug where tabfocus plugin would tab out of the editor instance even if the default action was prevented.
+    Fixed bug where tabfocus wouldn't tab properly to other adjacent editor instances.
+    Fixed bug where the DOMUtils setStyles wouldn't properly removed or update the data-mce-style attribute.
+    Fixed bug where dialog select boxes would be placed incorrectly if document.body wasn't statically positioned.
+    Fixed bug where pasting would sometimes scroll to the top of page if the user was using the autoresize plugin.
+    Fixed bug where caret wouldn't be properly rendered by Chrome when clicking on the iframes documentElement.
+    Fixed so custom images for menubutton/splitbutton can be provided. Patch contributed by Naim Hammadi.
+    Fixed so the default action of windows closing can be prevented by blocking the default action of the close event.
+    Fixed so nodeChange and focus of the editor isn't automatically performed when opening sub dialogs.
+Version 4.1.4 (2014-08-21)
+    Added new media_filter_html option to media plugin that blocks any conditional comments, scripts etc within a video element.
+    Added new content_security_policy option allows you to set custom policy for iframe contents. Patch contributed by Francois Chagnon.
+    Fixed bug where activate/deactivate events wasn't firing properly when switching between editors.
+    Fixed bug where placing the caret on iOS was difficult due to a WebKit bug with touch events.
+    Fixed bug where the resize helper wouldn't render properly on older IE versions.
+    Fixed bug where resizing images inside tables on older IE versions would sometimes fail depending mouse position.
+    Fixed bug where editor.insertContent would produce an exception when inserting select/option elements.
+    Fixed bug where extra empty paragraphs would be produced if block elements where inserted inside span elements.
+    Fixed bug where the spellchecker menu item wouldn't be properly checked if spell checking was started before it was rendered.
+    Fixed bug where the DomQuery filter function wouldn't remove non elements from collection.
+    Fixed bug where document with custom document.domain wouldn't properly render the editor.
+    Fixed bug where IE 8 would throw exception when trying to enter invalid color values into colorboxes.
+    Fixed bug where undo manager could incorrectly add an extra undo level when custom resize handles was removed.
+    Fixed bug where it wouldn't be possible to alter cell properties properly on table cells on IE 8.
+    Fixed so the color picker button in table dialog isn't shown unless you include the colorpicker plugin or add your own custom color picker.
+    Fixed so activate/deactivate events fire when windowManager opens a window since.
+    Fixed so the table advtab options isn't separated by an underscore to normalize naming with image_advtab option.
+    Fixed so the table cell dialog has proper padding when the advanced tab in disabled.
+Version 4.1.3 (2014-07-29)
+    Added event binding logic to tinymce.util.XHR making it possible to override headers and settings before any request is made.
+    Fixed bug where drag events wasn't fireing properly on older IE versions since the event handlers where bound to document.
+    Fixed bug where drag/dropping contents within the editor on IE would force the contents into plain text mode even if it was internal content.
+    Fixed bug where IE 7 wouldn't open menus properly due to a resize bug in the browser auto closing them immediately.
+    Fixed bug where the DOMUtils getPos logic wouldn't produce a valid coordinate inside the body if the body was positioned non static.
+    Fixed bug where the element path and format state wasn't properly updated if you had the wordcount plugin enabled.
+    Fixed bug where a comment at the beginning of source would produce an exception in the formatter logic.
+    Fixed bug where setAttrib/getAttrib on null would throw exception together with any hooked attributes like style.
+    Fixed bug where table sizes wasn't properly retained when copy/pasting on WebKit/Blink.
+    Fixed bug where WebKit/Blink would produce colors in RGB format instead of the forced HEX format when deleting contents.
+    Fixed bug where the width attribute wasn't updated on tables if you changed the size inside the table dialog.
+    Fixed bug where control selection wasn't properly handled when the caret was placed directly after an image.
+    Fixed bug where selecting the contents of table cells using the selection.select method wouldn't place the caret properly.
+    Fixed bug where the selection state for images wasn't removed when placing the caret right after an image on WebKit/Blink.
+    Fixed bug where all events wasn't properly unbound when and editor instance was removed or destroyed by some external innerHTML call.
+    Fixed bug where it wasn't possible or very hard to select images on iOS when the onscreen keyboard was visible.
+    Fixed so auto_focus can take a boolean argument this will auto focus the last initialized editor might be useful for single inits.
+    Fixed so word auto detect lists logic works better for faked lists that doesn't have specific markup.
+    Fixed so nodeChange gets fired on mouseup as it used to before 4.1.1 we optimized that event to fire less often.
+    Removed the finish menu item from spellchecker menu since it's redundant you can stop spellchecking by toggling menu item or button.
+Version 4.1.2 (2014-07-15)
+    Added offset/grep to DomQuery class works basically the same as it's jQuery equivalent.
+    Fixed bug where backspace/delete or setContent with an empty string would remove header data when using the fullpage plugin.
+    Fixed bug where tinymce.remove with a selector not matching any editors would remove all editors.
+    Fixed bug where resizing of the editor didn't work since the theme was calling setStyles instead of setStyle.
+    Fixed bug where IE 7 would fail to append html fragments to iframe document when using DomQuery.
+    Fixed bug where the getStyle DOMUtils method would produce an exception if it was called with null as it's element.
+    Fixed bug where the paste plugin would remove the element if the none of the paste_webkit_styles rules matched the current style.
+    Fixed bug where contextmenu table items wouldn't work properly on IE since it would some times fire an incorrect selection change.
+    Fixed bug where the padding/border values wasn't used in the size calculation for the body size when using autoresize. Patch contributed by Matt Whelan.
+    Fixed bug where conditional word comments wouldn't be properly removed when pasting plain text.
+    Fixed bug where resizing would sometime fail on IE 11 when the mouseup occurred inside the resizable element.
+    Fixed so the iframe gets initialized without any inline event handlers for better CSP support. Patch contributed by Matt Whelan.
+    Fixed so the tinymce.dom.Sizzle is the latest version of sizzle this resolves the document context bug.
+Version 4.1.1 (2014-07-08)
+    Fixed bug where pasting plain text on some WebKit versions would result in an empty line.
+    Fixed bug where resizing images inside tables on IE 11 wouldn't work properly.
+    Fixed bug where IE 11 would sometimes throw "Invalid argument" exception when editor contents was set to an empty string.
+    Fixed bug where document.activeElement would throw exceptions on IE 9 when that element was hidden or removed from dom.
+    Fixed bug where WebKit/Blink sometimes produced br elements with the Apple-interchange-newline class.
+    Fixed bug where table cell selection wasn't properly removed when copy/pasting table cells.
+    Fixed bug where pasting nested list items from Word wouldn't produce proper semantic nested lists.
+    Fixed bug where right clicking using the contextmenu plugin on WebKit/Blink on Mac OS X would select the target current word or line.
+    Fixed bug where it wasn't possible to alter table cell properties on IE 8 using the context menu.
+    Fixed bug where the resize helper wouldn't be correctly positioned on older IE versions.
+    Fixed bug where fullpage plugin would produce an error if you didn't specify a doctype encoding.
+    Fixed bug where anchor plugin would get the name/id of the current element even if it wasn't anchor element.
+    Fixed bug where visual aids for tables wouldn't be properly disabled when changing the border size.
+    Fixed bug where some control selection events wasn't properly fired on older IE versions.
+    Fixed bug where table cell selection on older IE versions would prevent resizing of images.
+    Fixed bug with paste_data_images paste option not working properly on modern IE versions.
+    Fixed bug where custom elements with underscores in the name wasn't properly parsed/serialized.
+    Fixed bug where applying inline formats to nested list elements would produce an incorrect formatting result.
+    Fixed so it's possible to hide items from elements path by using preventDefault/stopPropagation.
+    Fixed so inline mode toolbar gets rendered right aligned if the editable element positioned to the documents right edge.
+    Fixed so empty inline elements inside empty block elements doesn't get removed if configured to be kept intact.
+    Fixed so DomQuery parentsUntil/prevUntil/nextUntil supports selectors/elements/filters etc.
+    Fixed so legacyoutput plugin overrides fontselect and fontsizeselect controls and handles font elements properly.
+Version 4.1.0 (2014-06-18)
+    Added new file_picker_callback option to replace the old file_browser_callback the latter will still work though.
+    Added new custom colors to textcolor plugin will be displayed if a color picker is provided also shows the latest colors.
+    Added new color_picker_callback option to enable you to add custom color pickers to the editor.
+    Added new advanced tabs to table/cell/row dialogs to enable you to select colors for border/background.
+    Added new colorpicker plugin that lets you select colors from a hsv color picker.
+    Added new tinymce.util.Color class to handle color parsing and converting.
+    Added new colorpicker UI widget element lets you add a hsv color picker to any form/window.
+    Added new textpattern plugin that allows you to use markdown like text patterns to format contents.
+    Added new resize helper element that shows the current width & height while resizing.
+    Added new "once" method to Editor and EventDispatcher enables since callback execution events.
+    Added new jQuery like class under tinymce.dom.DomQuery it's exposed on editor instances (editor.$) and globally under (tinymce.$).
+    Fixed so the default resize method for images are proportional shift/ctrl can be used to make an unproportional size.
+    Fixed bug where the image_dimensions option of the image plugin would cause exceptions when it tried to update the size.
+    Fixed bug where table cell dialog class field wasn't properly updated when editing an a table cell with an existing class.
+    Fixed bug where Safari on Mac would produce webkit-fake-url for pasted images so these are now removed.
+    Fixed bug where the nodeChange event would get fired before the selection was changed when clicking inside the current selection range.
+    Fixed bug where valid_classes option would cause exception when it removed internal prefixed classes like mce-item-.
+    Fixed bug where backspace would cause navigation in IE 8 on an inline element and after a caret formatting was applied.
+    Fixed so placeholder images produced by the media plugin gets selected when inserted/edited.
+    Fixed so it's possible to drag in images when the paste_data_images option is enabled. Might be useful for mail clients.
+    Fixed so images doesn't get a width/height applied if the image_dimensions option is set to false useful for responsive contents.
+    Fixed so it's possible to pass in an optional arguments object for the nodeChanged function to be passed to all nodechange event listeners.
+    Fixed bug where media plugin embed code didn't update correctly.

File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/jquery.tinymce.min.js


+ 3 - 0
app/static/admin/tinymce/js/tinymce/langs/readme.md

@@ -0,0 +1,3 @@
+This is where language files should be placed.
+
+Please DO NOT translate these directly use this service: https://www.transifex.com/projects/p/tinymce/

+ 260 - 0
app/static/admin/tinymce/js/tinymce/langs/zh_CN.js

@@ -0,0 +1,260 @@
+tinymce.addI18n('zh_CN',{
+"Redo": "\u91cd\u590d",
+"Undo": "\u64a4\u6d88",
+"Cut": "\u526a\u5207",
+"Copy": "\u590d\u5236",
+"Paste": "\u7c98\u8d34",
+"Select all": "\u5168\u9009",
+"New document": "\u65b0\u6587\u6863",
+"Ok": "\u786e\u5b9a",
+"Cancel": "\u53d6\u6d88",
+"Visual aids": "\u7f51\u683c\u7ebf",
+"Bold": "\u7c97\u4f53",
+"Italic": "\u659c\u4f53",
+"Underline": "\u4e0b\u5212\u7ebf",
+"Strikethrough": "\u5220\u9664\u7ebf",
+"Superscript": "\u4e0a\u6807",
+"Subscript": "\u4e0b\u6807",
+"Clear formatting": "\u6e05\u9664\u683c\u5f0f",
+"Align left": "\u5de6\u5bf9\u9f50",
+"Align center": "\u5c45\u4e2d",
+"Align right": "\u53f3\u5bf9\u9f50",
+"Justify": "\u4e24\u7aef\u5bf9\u9f50",
+"Bullet list": "\u9879\u76ee\u7b26\u53f7",
+"Numbered list": "\u7f16\u53f7\u5217\u8868",
+"Decrease indent": "\u51cf\u5c11\u7f29\u8fdb",
+"Increase indent": "\u589e\u52a0\u7f29\u8fdb",
+"Close": "\u5173\u95ed",
+"Formats": "\u683c\u5f0f",
+"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u5bf9\u526a\u8d34\u677f\u7684\u8bbf\u95ee\uff0c\u8bf7\u4f7f\u7528Ctrl+X\/C\/V\u952e\u8fdb\u884c\u590d\u5236\u7c98\u8d34\u3002",
+"Headers": "\u6807\u9898",
+"Header 1": "\u6807\u98981",
+"Header 2": "\u6807\u98982",
+"Header 3": "\u6807\u98983",
+"Header 4": "\u6807\u98984",
+"Header 5": "\u6807\u98985",
+"Header 6": "\u6807\u98986",
+"Headings": "\u6807\u9898",
+"Heading 1": "\u6807\u98981",
+"Heading 2": "\u6807\u98982",
+"Heading 3": "\u6807\u98983",
+"Heading 4": "\u6807\u98984",
+"Heading 5": "\u6807\u98985",
+"Heading 6": "\u6807\u98986",
+"Div": "Div\u533a\u5757",
+"Pre": "\u9884\u683c\u5f0f\u6587\u672c",
+"Code": "\u4ee3\u7801",
+"Paragraph": "\u6bb5\u843d",
+"Blockquote": "\u5f15\u7528",
+"Inline": "\u6587\u672c",
+"Blocks": "\u533a\u5757",
+"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002",
+"Font Family": "\u5b57\u4f53",
+"Font Sizes": "\u5b57\u53f7",
+"Class": "Class",
+"Browse for an image": "\u6d4f\u89c8\u56fe\u50cf",
+"OR": "\u6216",
+"Drop an image here": "\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64",
+"Upload": "\u4e0a\u4f20",
+"Block": "\u5757",
+"Align": "\u5bf9\u9f50",
+"Default": "\u9ed8\u8ba4",
+"Circle": "\u7a7a\u5fc3\u5706",
+"Disc": "\u5b9e\u5fc3\u5706",
+"Square": "\u65b9\u5757",
+"Lower Alpha": "\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd",
+"Lower Greek": "\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd",
+"Lower Roman": "\u5c0f\u5199\u7f57\u9a6c\u5b57\u6bcd",
+"Upper Alpha": "\u5927\u5199\u82f1\u6587\u5b57\u6bcd",
+"Upper Roman": "\u5927\u5199\u7f57\u9a6c\u5b57\u6bcd",
+"Anchor": "\u951a\u70b9",
+"Name": "\u540d\u79f0",
+"Id": "\u6807\u8bc6\u7b26",
+"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u6807\u8bc6\u7b26\u5e94\u8be5\u4ee5\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u8ddf\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002",
+"You have unsaved changes are you sure you want to navigate away?": "\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f",
+"Restore last draft": "\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f",
+"Special character": "\u7279\u6b8a\u7b26\u53f7",
+"Source code": "\u6e90\u4ee3\u7801",
+"Insert\/Edit code sample": "\u63d2\u5165\/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b",
+"Language": "\u8bed\u8a00",
+"Code sample": "\u4ee3\u7801\u793a\u4f8b",
+"Color": "\u989c\u8272",
+"R": "R",
+"G": "G",
+"B": "B",
+"Left to right": "\u4ece\u5de6\u5230\u53f3",
+"Right to left": "\u4ece\u53f3\u5230\u5de6",
+"Emoticons": "\u8868\u60c5",
+"Document properties": "\u6587\u6863\u5c5e\u6027",
+"Title": "\u6807\u9898",
+"Keywords": "\u5173\u952e\u8bcd",
+"Description": "\u63cf\u8ff0",
+"Robots": "\u673a\u5668\u4eba",
+"Author": "\u4f5c\u8005",
+"Encoding": "\u7f16\u7801",
+"Fullscreen": "\u5168\u5c4f",
+"Action": "\u64cd\u4f5c",
+"Shortcut": "\u5feb\u6377\u952e",
+"Help": "\u5e2e\u52a9",
+"Address": "\u5730\u5740",
+"Focus to menubar": "\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f",
+"Focus to toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f",
+"Focus to element path": "\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84",
+"Focus to contextual toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355",
+"Insert link (if link plugin activated)": "\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
+"Save (if save plugin activated)": "\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
+"Find (if searchreplace plugin activated)": "\u641c\u7d22(\u5982\u679c\u641c\u7d22\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
+"Plugins installed ({0}):": "\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):",
+"Premium plugins:": "\u4f18\u79c0\u63d2\u4ef6\uff1a",
+"Learn more...": "\u4e86\u89e3\u66f4\u591a...",
+"You are using {0}": "\u4f60\u6b63\u5728\u4f7f\u7528 {0}",
+"Plugins": "\u63d2\u4ef6",
+"Handy Shortcuts": "\u521b\u5efa\u5feb\u6377\u65b9\u5f0f",
+"Horizontal line": "\u6c34\u5e73\u5206\u5272\u7ebf",
+"Insert\/edit image": "\u63d2\u5165\/\u7f16\u8f91\u56fe\u7247",
+"Image description": "\u56fe\u7247\u63cf\u8ff0",
+"Source": "\u5730\u5740",
+"Dimensions": "\u5927\u5c0f",
+"Constrain proportions": "\u4fdd\u6301\u7eb5\u6a2a\u6bd4",
+"General": "\u666e\u901a",
+"Advanced": "\u9ad8\u7ea7",
+"Style": "\u6837\u5f0f",
+"Vertical space": "\u5782\u76f4\u8fb9\u8ddd",
+"Horizontal space": "\u6c34\u5e73\u8fb9\u8ddd",
+"Border": "\u8fb9\u6846",
+"Insert image": "\u63d2\u5165\u56fe\u7247",
+"Image": "\u56fe\u7247",
+"Image list": "\u56fe\u7247\u5217\u8868",
+"Rotate counterclockwise": "\u9006\u65f6\u9488\u65cb\u8f6c",
+"Rotate clockwise": "\u987a\u65f6\u9488\u65cb\u8f6c",
+"Flip vertically": "\u5782\u76f4\u7ffb\u8f6c",
+"Flip horizontally": "\u6c34\u5e73\u7ffb\u8f6c",
+"Edit image": "\u7f16\u8f91\u56fe\u7247",
+"Image options": "\u56fe\u7247\u9009\u9879",
+"Zoom in": "\u653e\u5927",
+"Zoom out": "\u7f29\u5c0f",
+"Crop": "\u88c1\u526a",
+"Resize": "\u8c03\u6574\u5927\u5c0f",
+"Orientation": "\u65b9\u5411",
+"Brightness": "\u4eae\u5ea6",
+"Sharpen": "\u9510\u5316",
+"Contrast": "\u5bf9\u6bd4\u5ea6",
+"Color levels": "\u989c\u8272\u5c42\u6b21",
+"Gamma": "\u4f3d\u9a6c\u503c",
+"Invert": "\u53cd\u8f6c",
+"Apply": "\u5e94\u7528",
+"Back": "\u540e\u9000",
+"Insert date\/time": "\u63d2\u5165\u65e5\u671f\/\u65f6\u95f4",
+"Date\/time": "\u65e5\u671f\/\u65f6\u95f4",
+"Insert link": "\u63d2\u5165\u94fe\u63a5",
+"Insert\/edit link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
+"Text to display": "\u663e\u793a\u6587\u5b57",
+"Url": "\u5730\u5740",
+"Target": "\u6253\u5f00\u65b9\u5f0f",
+"None": "\u65e0",
+"New window": "\u5728\u65b0\u7a97\u53e3\u6253\u5f00",
+"Remove link": "\u5220\u9664\u94fe\u63a5",
+"Anchors": "\u951a\u70b9",
+"Link": "\u94fe\u63a5",
+"Paste or type a link": "\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5",
+"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7f00\u5417\uff1f",
+"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7f00\u5417\uff1f",
+"Link list": "\u94fe\u63a5\u5217\u8868",
+"Insert video": "\u63d2\u5165\u89c6\u9891",
+"Insert\/edit video": "\u63d2\u5165\/\u7f16\u8f91\u89c6\u9891",
+"Insert\/edit media": "\u63d2\u5165\/\u7f16\u8f91\u5a92\u4f53",
+"Alternative source": "\u955c\u50cf",
+"Poster": "\u5c01\u9762",
+"Paste your embed code below:": "\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:",
+"Embed": "\u5185\u5d4c",
+"Media": "\u5a92\u4f53",
+"Nonbreaking space": "\u4e0d\u95f4\u65ad\u7a7a\u683c",
+"Page break": "\u5206\u9875\u7b26",
+"Paste as text": "\u7c98\u8d34\u4e3a\u6587\u672c",
+"Preview": "\u9884\u89c8",
+"Print": "\u6253\u5370",
+"Save": "\u4fdd\u5b58",
+"Find": "\u67e5\u627e",
+"Replace with": "\u66ff\u6362\u4e3a",
+"Replace": "\u66ff\u6362",
+"Replace all": "\u5168\u90e8\u66ff\u6362",
+"Prev": "\u4e0a\u4e00\u4e2a",
+"Next": "\u4e0b\u4e00\u4e2a",
+"Find and replace": "\u67e5\u627e\u548c\u66ff\u6362",
+"Could not find the specified string.": "\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9.",
+"Match case": "\u533a\u5206\u5927\u5c0f\u5199",
+"Whole words": "\u5168\u5b57\u5339\u914d",
+"Spellcheck": "\u62fc\u5199\u68c0\u67e5",
+"Ignore": "\u5ffd\u7565",
+"Ignore all": "\u5168\u90e8\u5ffd\u7565",
+"Finish": "\u5b8c\u6210",
+"Add to Dictionary": "\u6dfb\u52a0\u5230\u5b57\u5178",
+"Insert table": "\u63d2\u5165\u8868\u683c",
+"Table properties": "\u8868\u683c\u5c5e\u6027",
+"Delete table": "\u5220\u9664\u8868\u683c",
+"Cell": "\u5355\u5143\u683c",
+"Row": "\u884c",
+"Column": "\u5217",
+"Cell properties": "\u5355\u5143\u683c\u5c5e\u6027",
+"Merge cells": "\u5408\u5e76\u5355\u5143\u683c",
+"Split cell": "\u62c6\u5206\u5355\u5143\u683c",
+"Insert row before": "\u5728\u4e0a\u65b9\u63d2\u5165",
+"Insert row after": "\u5728\u4e0b\u65b9\u63d2\u5165",
+"Delete row": "\u5220\u9664\u884c",
+"Row properties": "\u884c\u5c5e\u6027",
+"Cut row": "\u526a\u5207\u884c",
+"Copy row": "\u590d\u5236\u884c",
+"Paste row before": "\u7c98\u8d34\u5230\u4e0a\u65b9",
+"Paste row after": "\u7c98\u8d34\u5230\u4e0b\u65b9",
+"Insert column before": "\u5728\u5de6\u4fa7\u63d2\u5165",
+"Insert column after": "\u5728\u53f3\u4fa7\u63d2\u5165",
+"Delete column": "\u5220\u9664\u5217",
+"Cols": "\u5217",
+"Rows": "\u884c",
+"Width": "\u5bbd",
+"Height": "\u9ad8",
+"Cell spacing": "\u5355\u5143\u683c\u5916\u95f4\u8ddd",
+"Cell padding": "\u5355\u5143\u683c\u5185\u8fb9\u8ddd",
+"Caption": "\u6807\u9898",
+"Left": "\u5de6\u5bf9\u9f50",
+"Center": "\u5c45\u4e2d",
+"Right": "\u53f3\u5bf9\u9f50",
+"Cell type": "\u5355\u5143\u683c\u7c7b\u578b",
+"Scope": "\u8303\u56f4",
+"Alignment": "\u5bf9\u9f50\u65b9\u5f0f",
+"H Align": "\u6c34\u5e73\u5bf9\u9f50",
+"V Align": "\u5782\u76f4\u5bf9\u9f50",
+"Top": "\u9876\u90e8\u5bf9\u9f50",
+"Middle": "\u5782\u76f4\u5c45\u4e2d",
+"Bottom": "\u5e95\u90e8\u5bf9\u9f50",
+"Header cell": "\u8868\u5934\u5355\u5143\u683c",
+"Row group": "\u884c\u7ec4",
+"Column group": "\u5217\u7ec4",
+"Row type": "\u884c\u7c7b\u578b",
+"Header": "\u8868\u5934",
+"Body": "\u8868\u4f53",
+"Footer": "\u8868\u5c3e",
+"Border color": "\u8fb9\u6846\u989c\u8272",
+"Insert template": "\u63d2\u5165\u6a21\u677f",
+"Templates": "\u6a21\u677f",
+"Template": "\u6a21\u677f",
+"Text color": "\u6587\u5b57\u989c\u8272",
+"Background color": "\u80cc\u666f\u8272",
+"Custom...": "\u81ea\u5b9a\u4e49...",
+"Custom color": "\u81ea\u5b9a\u4e49\u989c\u8272",
+"No color": "\u65e0",
+"Table of Contents": "\u5185\u5bb9\u5217\u8868",
+"Show blocks": "\u663e\u793a\u533a\u5757\u8fb9\u6846",
+"Show invisible characters": "\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26",
+"Words: {0}": "\u5b57\u6570\uff1a{0}",
+"{0} words": "{0} \u5b57\u6570",
+"File": "\u6587\u4ef6",
+"Edit": "\u7f16\u8f91",
+"Insert": "\u63d2\u5165",
+"View": "\u89c6\u56fe",
+"Format": "\u683c\u5f0f",
+"Table": "\u8868\u683c",
+"Tools": "\u5de5\u5177",
+"Powered by {0}": "\u7531{0}\u9a71\u52a8",
+"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u5728\u7f16\u8f91\u533a\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9"
+});

+ 504 - 0
app/static/admin/tinymce/js/tinymce/license.txt

@@ -0,0 +1,504 @@
+      GNU LESSER GENERAL PUBLIC LICENSE
+           Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+          Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+      GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+          NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+         END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+

File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/advlist/plugin.min.js


+ 1 - 0
app/static/admin/tinymce/js/tinymce/plugins/anchor/plugin.min.js

@@ -0,0 +1 @@
+!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),e=function(t){return/^[A-Za-z][A-Za-z0-9\-:._]*$/.test(t)},n=function(t){var e=t.selection.getNode();return"A"===e.tagName&&""===t.dom.getAttrib(e,"href")?e.id||e.name:""},o=function(t,e){var n=t.selection.getNode();"A"===n.tagName&&""===t.dom.getAttrib(n,"href")?(n.removeAttribute("name"),n.id=e):(t.focus(),t.selection.collapse(!0),t.execCommand("mceInsertContent",!1,t.dom.createHTML("a",{id:e})))},r=function(t){var r=n(t);t.windowManager.open({title:"Anchor",body:{type:"textbox",name:"id",size:40,label:"Id",value:r},onsubmit:function(n){var r,a,i=n.data.id;r=t,(e(a=i)?(o(r,a),0):(r.windowManager.alert("Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores."),1))&&n.preventDefault()}})},a=function(t){t.addCommand("mceAnchor",function(){r(t)})},i=function(t){return function(e){for(var n=0;n<e.length;n++)(o=e[n]).attr("href")||!o.attr("id")&&!o.attr("name")||o.firstChild||e[n].attr("contenteditable",t);var o}},c=function(t){t.on("PreInit",function(){t.parser.addNodeFilter("a",i("false")),t.serializer.addNodeFilter("a",i(null))})},d=function(t){t.addButton("anchor",{icon:"anchor",tooltip:"Anchor",cmd:"mceAnchor",stateSelector:"a:not([href])"}),t.addMenuItem("anchor",{icon:"anchor",text:"Anchor",context:"insert",cmd:"mceAnchor"})};t.add("anchor",function(t){c(t),a(t),d(t)})}();

File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/autolink/plugin.min.js


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/autoresize/plugin.min.js


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/autosave/plugin.min.js


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/bbcode/plugin.min.js


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/charmap/plugin.min.js


+ 1 - 0
app/static/admin/tinymce/js/tinymce/plugins/code/plugin.min.js

@@ -0,0 +1 @@
+!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),n=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),e=function(t){return t.getParam("code_dialog_width",600)},o=function(t){return t.getParam("code_dialog_height",Math.min(n.DOM.getViewPort().h-200,500))},i=function(t,n){t.focus(),t.undoManager.transact(function(){t.setContent(n)}),t.selection.setCursorLocation(),t.nodeChanged()},c=function(t){return t.getContent({source_view:!0})},d=function(t){var n=e(t),d=o(t);t.windowManager.open({title:"Source code",body:{type:"textbox",name:"code",multiline:!0,minWidth:n,minHeight:d,spellcheck:!1,style:"direction: ltr; text-align: left"},onSubmit:function(n){i(t,n.data.code)}}).find("#code").value(c(t))},u=function(t){t.addCommand("mceCodeEditor",function(){d(t)})},a=function(t){t.addButton("code",{icon:"code",tooltip:"Source code",onclick:function(){d(t)}}),t.addMenuItem("code",{icon:"code",text:"Source code",onclick:function(){d(t)}})};t.add("code",function(t){return u(t),a(t),{}})}();

+ 138 - 0
app/static/admin/tinymce/js/tinymce/plugins/codesample/css/prism.css

@@ -0,0 +1,138 @@
+/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */
+/**
+ * prism.js default theme for JavaScript, CSS and HTML
+ * Based on dabblet (http://dabblet.com)
+ * @author Lea Verou
+ */
+
+code[class*="language-"],
+pre[class*="language-"] {
+  color: black;
+  text-shadow: 0 1px white;
+  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+  direction: ltr;
+  text-align: left;
+  white-space: pre;
+  word-spacing: normal;
+  word-break: normal;
+  word-wrap: normal;
+  line-height: 1.5;
+
+  -moz-tab-size: 4;
+  -o-tab-size: 4;
+  tab-size: 4;
+
+  -webkit-hyphens: none;
+  -moz-hyphens: none;
+  -ms-hyphens: none;
+  hyphens: none;
+}
+
+pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
+code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
+code[class*="language-"]::selection, code[class*="language-"] ::selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+@media print {
+  code[class*="language-"],
+  pre[class*="language-"] {
+    text-shadow: none;
+  }
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+  padding: 1em;
+  margin: .5em 0;
+  overflow: auto;
+}
+
+:not(pre) > code[class*="language-"],
+pre[class*="language-"] {
+  background: #f5f2f0;
+}
+
+/* Inline code */
+:not(pre) > code[class*="language-"] {
+  padding: .1em;
+  border-radius: .3em;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+  color: slategray;
+}
+
+.token.punctuation {
+  color: #999;
+}
+
+.namespace {
+  opacity: .7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted {
+  color: #905;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+  color: #690;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string {
+  color: #a67f59;
+  background: hsla(0, 0%, 100%, .5);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+  color: #07a;
+}
+
+.token.function {
+  color: #DD4A68;
+}
+
+.token.regex,
+.token.important,
+.token.variable {
+  color: #e90;
+}
+
+.token.important,
+.token.bold {
+  font-weight: bold;
+}
+.token.italic {
+  font-style: italic;
+}
+
+.token.entity {
+  cursor: help;
+}
+

File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/codesample/plugin.min.js


+ 1 - 0
app/static/admin/tinymce/js/tinymce/plugins/colorpicker/plugin.min.js

@@ -0,0 +1 @@
+!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),n=tinymce.util.Tools.resolve("tinymce.util.Color"),i=function(e,n){e.find("#preview")[0].getEl().style.background=n},t=function(e,t){var l=n(t),a=l.toRgb();e.fromJSON({r:a.r,g:a.g,b:a.b,hex:l.toHex().substr(1)}),i(e,l.toHex())},l=function(e,n,l){var a=e.windowManager.open({title:"Color",items:{type:"container",layout:"flex",direction:"row",align:"stretch",padding:5,spacing:10,items:[{type:"colorpicker",value:l,onchange:function(){var e=this.rgb();a&&(a.find("#r").value(e.r),a.find("#g").value(e.g),a.find("#b").value(e.b),a.find("#hex").value(this.value().substr(1)),i(a,this.value()))}},{type:"form",padding:0,labelGap:5,defaults:{type:"textbox",size:7,value:"0",flex:1,spellcheck:!1,onchange:function(){var e,n,i=a.find("colorpicker")[0];if(e=this.name(),n=this.value(),"hex"===e)return t(a,n="#"+n),void i.value(n);n={r:a.find("#r").value(),g:a.find("#g").value(),b:a.find("#b").value()},i.value(n),t(a,n)}},items:[{name:"r",label:"R",autofocus:1},{name:"g",label:"G"},{name:"b",label:"B"},{name:"hex",label:"#",value:"000000"},{name:"preview",type:"container",border:1}]}]},onSubmit:function(){n("#"+a.toJSON().hex)}});t(a,l)};e.add("colorpicker",function(e){e.settings.color_picker_callback||(e.settings.color_picker_callback=function(n,i){l(e,n,i)})})}();

+ 1 - 0
app/static/admin/tinymce/js/tinymce/plugins/contextmenu/plugin.min.js

@@ -0,0 +1 @@
+!function(){"use strict";var t=function(n){var e=n,o=function(){return e};return{get:o,set:function(t){e=t},clone:function(){return t(o())}}},n=tinymce.util.Tools.resolve("tinymce.PluginManager"),e=function(t){return{isContextMenuVisible:function(){return t.get()}}},o=function(t){return t.settings.contextmenu_never_use_native},i=function(t){return t.getParam("contextmenu","link openlink image inserttable | cell row column deletetable")},r=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),u=function(t){return r.DOM.select(t.settings.ui_container)[0]},c=function(t,n){return{x:t,y:n}},l=function(t,n,e){return c(t.x+n,t.y+e)},s=function(t,n){if(t&&"static"!==r.DOM.getStyle(t,"position",!0)){var e=r.DOM.getPos(t),o=e.x-t.scrollLeft,i=e.y-t.scrollTop;return l(n,-o,-i)}return l(n,0,0)},a=function(t,n){if(t.inline)return s(u(t),c((f=n).pageX,f.pageY));var e,o,i,a,f,m=(e=t.getContentAreaContainer(),o=c((a=n).clientX,a.clientY),i=r.DOM.getPos(e),l(o,i.x,i.y));return s(u(t),m)},f=tinymce.util.Tools.resolve("tinymce.ui.Factory"),m=tinymce.util.Tools.resolve("tinymce.util.Tools"),g=function(t,n,e,o){null===o.get()?o.set(function(t,n){var e,o,r=[];o=i(t),m.each(o.split(/[ ,]/),function(n){var e=t.menuItems[n];"|"===n&&(e={text:n}),e&&(e.shortcut="",r.push(e))});for(var c=0;c<r.length;c++)"|"===r[c].text&&(0!==c&&c!==r.length-1||r.splice(c,1));return(e=f.create("menu",{items:r,context:"contextmenu",classes:"contextmenu"})).uiContainer=u(t),e.renderTo(u(t)),e.on("hide",function(t){t.control===this&&n.set(!1)}),t.on("remove",function(){e.remove(),e=null}),e}(t,e)):o.get().show(),o.get().moveTo(n.x,n.y),e.set(!0)},v=function(t,n,e){t.on("contextmenu",function(i){var r;r=t,(!i.ctrlKey||o(r))&&(i.preventDefault(),g(t,a(t,i),n,e))})};n.add("contextmenu",function(n){var o=t(null),i=t(!1);return v(n,i,o),e(i)})}();

+ 1 - 0
app/static/admin/tinymce/js/tinymce/plugins/directionality/plugin.min.js

@@ -0,0 +1 @@
+!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),e=tinymce.util.Tools.resolve("tinymce.util.Tools"),i=function(t,i){var n,o=t.dom,c=t.selection.getSelectedBlocks();c.length&&(n=o.getAttrib(c[0],"dir"),e.each(c,function(t){o.getParent(t.parentNode,'*[dir="'+i+'"]',o.getRoot())||o.setAttrib(t,"dir",n!==i?i:null)}),t.nodeChanged())},n=function(t){t.addCommand("mceDirectionLTR",function(){i(t,"ltr")}),t.addCommand("mceDirectionRTL",function(){i(t,"rtl")})},o=function(t){var i=[];return e.each("h1 h2 h3 h4 h5 h6 div p".split(" "),function(e){i.push(e+"[dir="+t+"]")}),i.join(",")},c=function(t){t.addButton("ltr",{title:"Left to right",cmd:"mceDirectionLTR",stateSelector:o("ltr")}),t.addButton("rtl",{title:"Right to left",cmd:"mceDirectionRTL",stateSelector:o("rtl")})};t.add("directionality",function(t){n(t),c(t)})}();

BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-cool.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-cry.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-embarassed.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-foot-in-mouth.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-frown.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-innocent.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-kiss.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-laughing.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-money-mouth.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-sealed.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-smile.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-surprised.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-tongue-out.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-undecided.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-wink.gif


BIN
app/static/admin/tinymce/js/tinymce/plugins/emoticons/img/smiley-yell.gif


+ 1 - 0
app/static/admin/tinymce/js/tinymce/plugins/emoticons/plugin.min.js

@@ -0,0 +1 @@
+!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),e=tinymce.util.Tools.resolve("tinymce.util.Tools"),i=[["cool","cry","embarassed","foot-in-mouth"],["frown","innocent","kiss","laughing"],["money-mouth","sealed","smile","surprised"],["tongue-out","undecided","wink","yell"]],o=function(t){var o;return o='<table role="list" class="mce-grid">',e.each(i,function(i){o+="<tr>",e.each(i,function(e){var i=t+"/img/smiley-"+e+".gif";o+='<td><a href="#" data-mce-url="'+i+'" data-mce-alt="'+e+'" tabindex="-1" role="option" aria-label="'+e+'"><img src="'+i+'" style="width: 18px; height: 18px" role="presentation" /></a></td>'}),o+="</tr>"}),o+="</table>"},n=function(t,e){var i=o(e);t.addButton("emoticons",{type:"panelbutton",panel:{role:"application",autohide:!0,html:i,onclick:function(e){var i,o,n,a=t.dom.getParent(e.target,"a");a&&(i=t,o=a.getAttribute("data-mce-url"),n=a.getAttribute("data-mce-alt"),i.insertContent(i.dom.createHTML("img",{src:o,alt:n})),this.hide())}},tooltip:"Emoticons"})};t.add("emoticons",function(t,e){n(t,e)})}();

File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/fullpage/plugin.min.js


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/fullscreen/plugin.min.js


BIN
app/static/admin/tinymce/js/tinymce/plugins/help/img/logo.png


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/help/plugin.min.js


+ 1 - 0
app/static/admin/tinymce/js/tinymce/plugins/hr/plugin.min.js

@@ -0,0 +1 @@
+!function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=function(n){n.addCommand("InsertHorizontalRule",function(){n.execCommand("mceInsertContent",!1,"<hr />")})},o=function(n){n.addButton("hr",{icon:"hr",tooltip:"Horizontal line",cmd:"InsertHorizontalRule"}),n.addMenuItem("hr",{icon:"hr",text:"Horizontal line",cmd:"InsertHorizontalRule",context:"insert"})};n.add("hr",function(n){t(n),o(n)})}();

File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/image/plugin.min.js


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/imagetools/plugin.min.js


File diff suppressed because it is too large
+ 82 - 0
app/static/admin/tinymce/js/tinymce/plugins/imageupload/css/style.css


BIN
app/static/admin/tinymce/js/tinymce/plugins/imageupload/img/icon.png


+ 91 - 0
app/static/admin/tinymce/js/tinymce/plugins/imageupload/jquery.iframe-post-form.js

@@ -0,0 +1,91 @@
+/**
+ * jQuery plugin for posting form including file inputs.
+ *
+ * Copyright (c) 2010 - 2011 Ewen Elder
+ *
+ * Licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * @author: Ewen Elder <ewen at jainaewen dot com> <glomainn at yahoo dot co dot uk>
+ * @version: 1.1.1 (2011-07-29)
+**/
+(function ($)
+{
+	$.fn.iframePostForm = function (options)
+	{
+		var response,
+			returnReponse,
+			element,
+			status = true,
+			iframe;
+
+		options = $.extend({}, $.fn.iframePostForm.defaults, options);
+
+
+		// Add the iframe.
+		if (!$('#' + options.iframeID).length)
+		{
+			$('body').append('<iframe id="' + options.iframeID + '" name="' + options.iframeID + '" style="display:none" />');
+		}
+
+
+		return $(this).each(function ()
+		{
+			element = $(this);
+
+
+			// Target the iframe.
+			element.attr('target', options.iframeID);
+
+
+			// Submit listener.
+			element.submit(function ()
+			{
+				// If status is false then abort.
+				status = options.post.apply(this);
+
+				if (status === false)
+				{
+					return status;
+				}
+
+
+				iframe = $('#' + options.iframeID).load(function ()
+				{
+					response = iframe.contents().find('body');
+
+					if (options.json)
+					{
+						returnReponse = $.parseJSON(response.text());
+					}
+
+					else
+					{
+						returnReponse = response.html();
+					}
+
+
+					options.complete.apply(this, [returnReponse]);
+
+					iframe.unbind('load');
+
+
+					setTimeout(function ()
+					{
+						response.html('');
+					}, 1);
+				});
+			});
+		});
+	};
+
+
+	$.fn.iframePostForm.defaults =
+	{
+		iframeID : 'iframe-post-form',       // Iframe ID.
+		json : false,                        // Parse server response as a json object.
+		post : function () {},               // Form onsubmit.
+		complete : function (response) {}    // After response from the server has been received.
+	};
+})(jQuery);

+ 89 - 0
app/static/admin/tinymce/js/tinymce/plugins/imageupload/plugin.min.js

@@ -0,0 +1,89 @@
+(function () {
+    tinymce.create("tinymce.plugins.ImageUploadPlugin", {
+        init: function (e, t) {
+            t = tinyMCE.activeEditor.getParam("imageupload_rel") || t;
+            var n = tinyMCE.activeEditor.getParam("imageupload_url");
+            var r = document.getElementsByTagName("body")[0];
+            var i = document.createElement("link");
+            i.type = "text/css";
+            i.rel = "stylesheet";
+            i.href = t + "/css/style.css";
+            r.appendChild(i);
+            var j = document.createElement("script");
+            j.src = t + "/jquery.iframe-post-form.js";
+            r.appendChild(j);
+            e.addCommand("mceImageUpload", function () {
+                $("#image_upload_type").val("tinymce");
+                $("body").append('<div class="imageUploadBg"></div>');
+                var t = function (e) {
+                    $(".imageUploadError").html(e).show();
+                    r();
+                };
+                var r = function () {
+                    $(".imageUploadFg").remove();
+                    $(".imageUploadFgLoading").remove()
+                };
+                var i = function () {
+                    $(".imageUploadBg").remove();
+                    $(".imageUploadContainer").remove()
+                };
+                var s = '<div class="imageUploadContainer mce-container mce-panel"><div class="imageUploadContainerInner"><div class="mce-window-head"><div class="mce-title">Upload Image</div><button type="button" class="mce-close">×</button></div><form action="' + n + '" method="POST"  enctype="multipart/form-data" id="uploadImageForm"><div class="mce-container imageUploadFormContainer" hidefocus="1" tabindex="-1"><div class="mce-container-body"><label for="image-upload-area">Select a file</label><input type="file" name="file" id="image-upload-area" class="mce-textbox mce-placeholder" hidefocus="true" style="width: 258px;"></div><p class="imageUploadError"></p></div></form><div class="imageUploadConfirmCase mce-panel"><div class="mce-btn mce-primary"><button role="presentation" class="imageUploadSubmit">Upload</button></div><div class="mce-btn"><button role="presentation" class="imageUploadClose">Close</button></div></div></div></div>';
+                $("body").append(s);
+                $(".imageUploadBg, .imageUploadContainer .imageUploadClose, .mce-close").on("click", function () {
+                    r();
+                    i()
+                });
+                $("#uploadImageForm").iframePostForm({
+                    json: true, post: function () {
+                    }, complete: function (n) {
+                        if (typeof n != "object" || n == null || typeof n.error == "undefined") {
+                            r();
+                            t("An error occurred while uploading your image.")
+                        } else {
+                            if (n.error != false) {
+                                switch (n.error) {
+                                    case"filetype":
+                                        t("Please select a valid image and try again.");
+                                        break;
+                                    default:
+                                        t("An unknown error occurred.");
+                                        break
+                                }
+                            } else {
+                                if (typeof n.path != "undefined") {
+                                    var s = '<img src="%s" />';
+                                    e.insertContent(s.replace("%s", n.path));
+                                    e.focus();
+                                    r();
+                                    i()
+                                } else {
+                                    t("An unknown error occurred.")
+                                }
+                            }
+                        }
+                    }
+                });
+                $(".imageUploadSubmit").on("click", function () {
+                    $(".imageUploadError").html("").hide();
+                    if ($("#image-upload-area").val() != "") {
+                        $("body").append('<div class="imageUploadFg"></div>');
+                        $("body").append('<div class="imageUploadFgLoading"></div>');
+                        $("#uploadImageForm").submit()
+                    } else {
+                        t("Please select an image to upload.")
+                    }
+                })
+            });
+            e.addButton("imageupload", {title: "Image Upload", cmd: "mceImageUpload", image: t + "/img/icon.png"})
+        }, getInfo: function () {
+            return {
+                longname: "Image Upload",
+                author: "BoxUK",
+                authorurl: "https://github.com/boxuk/tinymce-imageupload",
+                infourl: "https://github.com/boxuk/tinymce-imageupload/blob/master/README.md",
+                version: "1.0.0"
+            }
+        }
+    });
+    tinymce.PluginManager.add("imageupload", tinymce.plugins.ImageUploadPlugin)
+})();

File diff suppressed because it is too large
+ 82 - 0
app/static/admin/tinymce/js/tinymce/plugins/imageupload/uncompressed/css/style.css


+ 138 - 0
app/static/admin/tinymce/js/tinymce/plugins/imageupload/uncompressed/plugin.js

@@ -0,0 +1,138 @@
+(function() {
+    tinymce.create('tinymce.plugins.ImageUploadPlugin', {
+        init : function(ed, url) {
+            url = tinyMCE.activeEditor.getParam('imageupload_rel') || url;
+            var imageUploadUrl = tinyMCE.activeEditor.getParam('imageupload_url');
+            var head = document.getElementsByTagName('body')[0];
+            var css = document.createElement('link');                       
+            css.type = 'text/css';
+            css.rel = 'stylesheet';
+            css.href = url + '/css/style.css';
+            head.appendChild(css);
+            
+            // Register commands
+            ed.addCommand('mceImageUpload', function() {
+                $('#image_upload_type').val('tinymce'); 
+                $('body').append('<div class="imageUploadBg"></div>');
+                
+                var showImageUploadError = function(msg) {
+                    $('.imageUploadError').html(msg).show();
+                    removeForeground();
+                };
+                
+                var removeForeground = function() {
+                    $('.imageUploadFg').remove();
+                    $('.imageUploadFgLoading').remove();
+                };
+                
+                var removeBackground = function() {
+                    $('.imageUploadBg').remove();
+                    $('.imageUploadContainer').remove();
+                };
+                
+                var container = '\
+                    <div class="imageUploadContainer mce-container mce-panel mce-window">\
+                        <div class="imageUploadContainerInner">\
+                            <div class="mce-window-head">\
+                                <div class="mce-title">Upload Image</div>\
+                                <button type="button" class="mce-close">×</button>\
+                            </div>\
+                            <form action="' + imageUploadUrl + '" method="POST"  enctype="multipart/form-data" id="uploadImageForm">\
+                            <div class="mce-container imageUploadFormContainer" hidefocus="1" tabindex="-1">\
+                                <div class="mce-container-body">\
+                                    <label for="image-upload-area">Select a file</label>\
+                                    <input type="file" name="file" id="image-upload-area" class="mce-textbox mce-placeholder" hidefocus="true" style="width: 258px;">\
+                                </div>\
+                                <p class="imageUploadError"></p>\
+                            </div>\
+                            </form>\
+                            <div class="imageUploadConfirmCase mce-panel">\
+                                <div class="mce-btn mce-primary">\
+                                    <button role="presentation" class="imageUploadSubmit">Upload</button>\
+                                </div>\
+                                <div class="mce-btn">\
+                                    <button role="presentation" class="imageUploadClose">Close</button>\
+                                </div>\
+                            </div>\
+                        </div>\
+                    </div>\
+                ';
+                
+                $('body').append(container);
+                
+                $('.imageUploadBg, .imageUploadContainer .imageUploadClose, .mce-close').on('click', function(){
+                    removeForeground();
+                    removeBackground();
+                });
+                
+                $('#uploadImageForm').iframePostForm({
+                    json: true,
+                    post: function(){
+                        // Sending.
+                    },
+                    complete: function(response){
+
+                        if (typeof response != "object" || response == null || typeof response.error == 'undefined') {
+                            removeForeground();
+                            showImageUploadError('An error occurred while uploading your image.');
+                        } else {
+                            if (response.error != false) {
+                                switch (response.error) {
+                                    case ("filetype"):
+                                        showImageUploadError('Please select a valid image and try again.');
+                                        break;
+                                    default:
+                                        showImageUploadError('An unknown error occurred.');
+                                        break;
+                                }
+                            } else {
+                                if (typeof response.path != 'undefined') {
+                                    var tpl = '<img src="%s" />';
+                                    ed.insertContent(tpl.replace('%s', response.path));
+                                    ed.focus();
+                                    removeForeground();
+                                    removeBackground();
+                                } else {
+                                    showImageUploadError('An unknown error occurred.');
+                                }
+                            }
+                        }
+                    }
+                });
+                
+                $('.imageUploadSubmit').on('click', function(){
+                    
+                    $('.imageUploadError').html('').hide();
+                    
+                    if ($('#image-upload-area').val() != '') {
+                        $('body').append('<div class="imageUploadFg"></div>');
+                        $('body').append('<div class="imageUploadFgLoading"></div>');
+                        $('#uploadImageForm').submit();
+                    } else {
+                        showImageUploadError('Please select an image to upload.');
+                    }
+                    
+                });
+            });
+
+            // Register buttons
+            ed.addButton('imageupload', {
+                title : 'Image Upload',
+                cmd : 'mceImageUpload',
+                image : url + '/img/icon.png'
+            });
+        },
+
+        getInfo : function() {
+            return {
+                longname : 'Image Upload',
+                author : 'BoxUK',
+                authorurl : 'https://github.com/boxuk/tinymce-imageupload',
+                infourl : 'https://github.com/boxuk/tinymce-imageupload/blob/master/README.md',
+                version : '1.0.0'
+            };
+        }
+    });
+
+    tinymce.PluginManager.add('imageupload', tinymce.plugins.ImageUploadPlugin);
+})();

File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/importcss/plugin.min.js


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/insertdatetime/plugin.min.js


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/legacyoutput/plugin.min.js


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/link/plugin.min.js


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/lists/plugin.min.js


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/media/plugin.min.js


+ 1 - 0
app/static/admin/tinymce/js/tinymce/plugins/nonbreaking/plugin.min.js

@@ -0,0 +1 @@
+!function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager"),e=function(n,e){var t,a=(t=n).plugins.visualchars&&t.plugins.visualchars.isEnabled()?'<span class="mce-nbsp">&nbsp;</span>':"&nbsp;";n.insertContent(function(n,e){for(var t="",a=0;a<e;a++)t+=n;return t}(a,e)),n.dom.setAttrib(n.dom.select("span.mce-nbsp"),"data-mce-bogus","1")},t=function(n){n.addCommand("mceNonBreaking",function(){e(n,1)})},a=tinymce.util.Tools.resolve("tinymce.util.VK"),i=function(n){var e=n.getParam("nonbreaking_force_tab",0);return"boolean"==typeof e?!0===e?3:0:e},o=function(n){var t=i(n);t>0&&n.on("keydown",function(i){if(i.keyCode===a.TAB&&!i.isDefaultPrevented()){if(i.shiftKey)return;i.preventDefault(),i.stopImmediatePropagation(),e(n,t)}})},r=function(n){n.addButton("nonbreaking",{title:"Nonbreaking space",cmd:"mceNonBreaking"}),n.addMenuItem("nonbreaking",{text:"Nonbreaking space",cmd:"mceNonBreaking",context:"insert"})};n.add("nonbreaking",function(n){t(n),r(n),o(n)})}();

+ 1 - 0
app/static/admin/tinymce/js/tinymce/plugins/noneditable/plugin.min.js

@@ -0,0 +1 @@
+!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),n=tinymce.util.Tools.resolve("tinymce.util.Tools"),e=function(t){return t.getParam("noneditable_noneditable_class","mceNonEditable")},r=function(t){return t.getParam("noneditable_editable_class","mceEditable")},a=function(t){var n=t.getParam("noneditable_regexp",[]);return n&&n.constructor===RegExp?[n]:n},i=function(t){return function(n){return-1!==(" "+n.attr("class")+" ").indexOf(t)}},o=function(t,n,e){return function(r){var a=arguments,i=a[a.length-2],o=i>0?n.charAt(i-1):"";if('"'===o)return r;if(">"===o){var c=n.lastIndexOf("<",i);if(-1!==c&&-1!==n.substring(c,i).indexOf('contenteditable="false"'))return r}return'<span class="'+e+'" data-mce-content="'+t.dom.encode(a[0])+'">'+t.dom.encode("string"==typeof a[1]?a[1]:a[0])+"</span>"}},c=function(t){var c,l,u="contenteditable";c=" "+n.trim(r(t))+" ",l=" "+n.trim(e(t))+" ";var f=i(c),s=i(l),d=a(t);t.on("PreInit",function(){d.length>0&&t.on("BeforeSetContent",function(n){!function(t,n,r){var a=n.length,i=r.content;if("raw"!==r.format){for(;a--;)i=i.replace(n[a],o(t,i,e(t)));r.content=i}}(t,d,n)}),t.parser.addAttributeFilter("class",function(t){for(var n,e=t.length;e--;)n=t[e],f(n)?n.attr(u,"true"):s(n)&&n.attr(u,"false")}),t.serializer.addAttributeFilter(u,function(t){for(var n,e=t.length;e--;)n=t[e],(f(n)||s(n))&&(d.length>0&&n.attr("data-mce-content")?(n.name="#text",n.type=3,n.raw=!0,n.value=n.attr("data-mce-content")):n.attr(u,null))})})};t.add("noneditable",function(t){c(t)})}();

+ 1 - 0
app/static/admin/tinymce/js/tinymce/plugins/pagebreak/plugin.min.js

@@ -0,0 +1 @@
+!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=tinymce.util.Tools.resolve("tinymce.Env"),n=function(e){return e.getParam("pagebreak_separator","\x3c!-- pagebreak --\x3e")},t=function(e){return e.getParam("pagebreak_split_block",!1)},r=function(){return"mce-pagebreak"},c=function(){return'<img src="'+a.transparentSrc+'" class="mce-pagebreak" data-mce-resize="false" data-mce-placeholder />'},o=function(e){var a=n(e),r=new RegExp(a.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,function(e){return"\\"+e}),"gi");e.on("BeforeSetContent",function(e){e.content=e.content.replace(r,c())}),e.on("PreInit",function(){e.serializer.addNodeFilter("img",function(n){for(var r,c,o=n.length;o--;)if((c=(r=n[o]).attr("class"))&&-1!==c.indexOf("mce-pagebreak")){var i=r.parent;if(e.schema.getBlockElements()[i.name]&&t(e)){i.type=3,i.value=a,i.raw=!0,r.remove();continue}r.type=3,r.value=a,r.raw=!0}})})},i=c,g=r,u=function(e){e.addCommand("mcePageBreak",function(){e.settings.pagebreak_split_block?e.insertContent("<p>"+i()+"</p>"):e.insertContent(i())})},m=function(e){e.on("ResolveName",function(a){"IMG"===a.target.nodeName&&e.dom.hasClass(a.target,g())&&(a.name="pagebreak")})},s=function(e){e.addButton("pagebreak",{title:"Page break",cmd:"mcePageBreak"}),e.addMenuItem("pagebreak",{text:"Page break",icon:"pagebreak",cmd:"mcePageBreak",context:"insert"})};e.add("pagebreak",function(e){u(e),s(e),o(e),m(e)})}();

File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/paste/plugin.min.js


File diff suppressed because it is too large
+ 0 - 0
app/static/admin/tinymce/js/tinymce/plugins/preview/plugin.min.js


+ 1 - 0
app/static/admin/tinymce/js/tinymce/plugins/print/plugin.min.js

@@ -0,0 +1 @@
+!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),n=function(t){t.addCommand("mcePrint",function(){t.getWin().print()})},i=function(t){t.addButton("print",{title:"Print",cmd:"mcePrint"}),t.addMenuItem("print",{text:"Print",cmd:"mcePrint",icon:"print"})};t.add("print",function(t){n(t),i(t),t.addShortcut("Meta+P","","mcePrint")})}();

+ 1 - 0
app/static/admin/tinymce/js/tinymce/plugins/save/plugin.min.js

@@ -0,0 +1 @@
+!function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager"),e=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),t=tinymce.util.Tools.resolve("tinymce.util.Tools"),a=function(n){return n.getParam("save_enablewhendirty",!0)},o=function(n){return!!n.getParam("save_onsavecallback")},c=function(n){return!!n.getParam("save_oncancelcallback")},i=function(n,e){n.notificationManager.open({text:n.translate(e),type:"error"})},r=function(n){var t;if(t=e.DOM.getParent(n.id,"form"),!a(n)||n.isDirty()){if(n.save(),o(n))return n.execCallback("save_onsavecallback",n),void n.nodeChanged();t?(n.setDirty(!1),t.onsubmit&&!t.onsubmit()||("function"==typeof t.submit?t.submit():i(n,"Error: Form submit field collision.")),n.nodeChanged()):i(n,"Error: No form element found.")}},l=function(n){var e=t.trim(n.startContent);c(n)?n.execCallback("save_oncancelcallback",n):(n.setContent(e),n.undoManager.clear(),n.nodeChanged())},d=function(n){n.addCommand("mceSave",function(){r(n)}),n.addCommand("mceCancel",function(){l(n)})},s=function(n){return function(e){var t=e.control;n.on("nodeChange dirty",function(){t.disabled(a(n)&&!n.isDirty())})}},u=function(n){n.addButton("save",{icon:"save",text:"Save",cmd:"mceSave",disabled:!0,onPostRender:s(n)}),n.addButton("cancel",{text:"Cancel",icon:!1,cmd:"mceCancel",disabled:!0,onPostRender:s(n)}),n.addShortcut("Meta+S","","mceSave")};n.add("save",function(n){u(n),d(n)})}();

Some files were not shown because too many files changed in this diff