فهرست منبع

Merge pull request #1018 from 2du/beta

스테이블로
잉여개발기 (SPDV) 5 سال پیش
والد
کامیت
a310eec960
58فایلهای تغییر یافته به همراه1188 افزوده شده و 787 حذف شده
  1. 2 2
      .github/PULL_REQUEST_TEMPLATE.md
  2. 55 26
      app.py
  3. 31 25
      emergency_tool.py
  4. 24 5
      language/en-US.json
  5. 44 26
      language/ko-KR.json
  6. 16 45
      readme-en.md
  7. 13 15
      readme.md
  8. 1 1
      requirements.txt
  9. 1 1
      route/api_markup.py
  10. 2 4
      route/api_raw.py
  11. 16 7
      route/api_recent_change.py
  12. 7 7
      route/api_version.py
  13. 1 5
      route/api_w.py
  14. 6 1
      route/edit.py
  15. 9 9
      route/edit_delete.py
  16. 3 37
      route/edit_many_delete.py
  17. 4 2
      route/edit_move.py
  18. 13 13
      route/func_upload.py
  19. 11 11
      route/give_acl.py
  20. 2 1
      route/give_history_add.py
  21. 11 6
      route/give_user_ban.py
  22. 3 3
      route/inter_wiki.py
  23. 2 2
      route/inter_wiki_del.py
  24. 11 8
      route/inter_wiki_plus.py
  25. 27 14
      route/login.py
  26. 74 0
      route/login_2fa.py
  27. 6 2
      route/login_check_key.py
  28. 1 2
      route/login_register.py
  29. 2 4
      route/main_file.py
  30. 5 3
      route/main_image_view.py
  31. 3 0
      route/main_other.py
  32. 25 19
      route/main_views.py
  33. 8 4
      route/recent_changes.py
  34. 20 42
      route/recent_history_tool.py
  35. 2 5
      route/server_now_update.py
  36. 20 10
      route/setting.py
  37. 86 70
      route/tool/func.py
  38. 0 1
      route/tool/mark.py
  39. 84 21
      route/tool/set_mark/markdown.py
  40. 183 227
      route/tool/set_mark/namumark.py
  41. 14 14
      route/tool/set_mark/tool.py
  42. 4 3
      route/user_custom_head_view.py
  43. 41 9
      route/user_setting.py
  44. 2 5
      route/view_read.py
  45. 41 0
      route/vote.py
  46. 62 0
      route/vote_add.py
  47. 29 0
      route/vote_close.py
  48. 47 0
      route/vote_end.py
  49. 66 0
      route/vote_select.py
  50. 5 9
      version.json
  51. 3 1
      views/main_css/css/main.css
  52. 12 0
      views/main_css/css/sub/dark.css
  53. 0 16
      views/main_css/js/load_namumark.js
  54. 1 3
      views/main_css/js/load_skin_set.js
  55. 4 31
      views/marisa/css/dark.css
  56. 17 7
      views/marisa/index.html
  57. 2 2
      views/marisa/info.json
  58. 4 1
      views/marisa/js/skin_set.js

+ 2 - 2
.github/PULL_REQUEST_TEMPLATE.md

@@ -1,4 +1,4 @@
 <!--
-stable branch로 요청을 보내지 마십시오. 개발은 master branch에서 이루어집니다.
-Don't request merge your commit to stable branch, please request to master branch.
+stable, beta branch로 요청을 보내지 마십시오. 개발은 dev branch에서 이루어집니다.
+Don't request merge your commit to stable, beta branch, please request to dev branch.
 -->

+ 55 - 26
app.py

@@ -1,3 +1,4 @@
+# Load
 import os
 import re
 
@@ -8,14 +9,15 @@ for i_data in os.listdir("route"):
 
         exec("from route." + f_src + " import *")
 
-# DB
+# Version
 version_list = json.loads(open('version.json', encoding = 'utf8').read())
 
-print('Version : ' + version_list['master']['r_ver'])
-print('DB set version : ' + version_list['master']['c_ver'])
-print('Skin set version : ' + version_list['master']['s_ver'])
+print('Version : ' + version_list['beta']['r_ver'])
+print('DB set version : ' + version_list['beta']['c_ver'])
+print('Skin set version : ' + version_list['beta']['s_ver'])
 print('----')
 
+# DB
 while 1:
     try:
         set_data = json.loads(open('data/set.json', encoding = 'utf8').read())
@@ -79,35 +81,39 @@ if set_data['db_type'] == 'mysql':
     try:
         set_data_mysql = json.loads(open('data/mysql.json', encoding = 'utf8').read())
     except:
-        new_json = ['', '', '']
+        new_json = {}
 
         while 1:
             print('DB user ID : ', end = '')
-            new_json[0] = str(input())
-            if new_json[0] != '':
+            new_json['user'] = str(input())
+            if new_json['user'] != '':
                 break
 
         while 1:
             print('DB password : ', end = '')
-            new_json[1] = str(input())
-            if new_json[1] != '':
+            new_json['password'] = str(input())
+            if new_json['password'] != '':
                 break
                 
         print('DB host (localhost) : ', end = '')
-        new_json[2] = str(input())
-        if new_json[2] == '':
-            new_json[2] == 'localhost'
+        new_json['host'] = str(input())
+        new_json['host'] = 'localhost' if new_json['host'] == '' else new_json['host']
+
+        print('DB port (3306) : ', end = '')
+        new_json['port'] = str(input())
+        new_json['port'] = '3306' if new_json['port'] == '' else new_json['port']
 
         with open('data/mysql.json', 'w', encoding = 'utf8') as f:
-            f.write('{ "user" : "' + new_json[0] + '", "password" : "' + new_json[1] + '", "host" : "' + new_json[2] + '" }')
+            f.write(json.dumps(new_json))
 
-        set_data_mysql = json.loads(open('data/mysql.json', encoding = 'utf8').read())
+        set_data_mysql = new_json
 
     conn = pymysql.connect(
         host = set_data_mysql['host'] if 'host' in set_data_mysql else 'localhost',
         user = set_data_mysql['user'],
         password = set_data_mysql['password'],
-        charset = 'utf8mb4'
+        charset = 'utf8mb4',
+        port = int(set_data_mysql['port']) if 'port' in set_data_mysql else 3306
     )
     curs = conn.cursor()
 
@@ -123,6 +129,7 @@ else:
 
 load_conn(conn)
 
+# DB init
 create_data = {}
 create_data['data'] = ['title', 'data']
 create_data['cache_data'] = ['title', 'data', 'id']
@@ -141,12 +148,10 @@ create_data['alist'] = ['name', 'acl']
 create_data['re_admin'] = ['who', 'what', 'time']
 create_data['alarm'] = ['name', 'data', 'date']
 create_data['ua_d'] = ['name', 'ip', 'ua', 'today', 'sub']
-create_data['filter'] = ['name', 'regex', 'sub']
 create_data['scan'] = ['user', 'title', 'type']
 create_data['acl'] = ['title', 'decu', 'dis', 'view', 'why']
-create_data['inter'] = ['title', 'link', 'icon']
-create_data['html_filter'] = ['html', 'kind', 'plus']
-create_data['oauth_conn'] = ['provider', 'wiki_id', 'sns_id', 'name', 'picture']
+create_data['html_filter'] = ['html', 'kind', 'plus', 'plus_t']
+create_data['vote'] = ['name', 'id', 'subject', 'data', 'user', 'type', 'acl']
 for i in create_data:
     try:
         curs.execute(db_change('select test from ' + i + ' limit 1'))
@@ -163,7 +168,7 @@ try:
     if not ver_set_data:
         setup_tool = 2
     else:
-        if int(version_list['master']['c_ver']) > int(ver_set_data[0][0]):
+        if int(version_list['beta']['c_ver']) > int(ver_set_data[0][0]):
             setup_tool = 1
 except:
     setup_tool = 2
@@ -182,7 +187,7 @@ if setup_tool != 0:
         set_init()
 
 curs.execute(db_change('delete from other where name = "ver"'))
-curs.execute(db_change('insert into other (name, data) values ("ver", ?)'), [version_list['master']['c_ver']])
+curs.execute(db_change('insert into other (name, data) values ("ver", ?)'), [version_list['beta']['c_ver']])
 conn.commit()
 
 # Init
@@ -395,7 +400,7 @@ def server_restart():
 
 @app.route('/update', methods=['GET', 'POST'])
 def server_now_update():
-    return server_now_update_2(conn, version_list['master']['r_ver'])
+    return server_now_update_2(conn, version_list['beta']['r_ver'])
 
 @app.route('/xref/<everything:name>')
 def view_xref(name = None):
@@ -463,7 +468,7 @@ def main_other():
 @app.route('/manager', methods=['POST', 'GET'])
 @app.route('/manager/<int:num>', methods=['POST', 'GET'])
 def main_manager(num = 1):
-    return main_manager_2(conn, num, version_list['master']['r_ver'])
+    return main_manager_2(conn, num, version_list['beta']['r_ver'])
 
 @app.route('/title_index')
 def list_title_index():
@@ -513,6 +518,10 @@ def topic_close_list(name = 'test'):
 def user_tool(name = None):
     return user_tool_2(conn, name)
 
+@app.route('/2fa_login', methods=['POST', 'GET'])
+def login_2fa():
+    return login_2fa_2(conn)
+
 @app.route('/login', methods=['POST', 'GET'])
 def login():
     return login_2(conn)
@@ -617,7 +626,7 @@ def user_count_edit(name = None):
 def func_title_random():
     return func_title_random_2(conn)
 
-@app.route('/image/<name>')
+@app.route('/image/<everything:name>')
 def main_image_view(name = None):
     return main_image_view_2(conn, name, app_var)
 
@@ -634,6 +643,26 @@ def application_submitted():
 def applications():
     return applications_2(conn)
 
+@app.route('/vote/<num>', methods=['POST', 'GET'])
+def vote_select(num = '1'):
+    return vote_select_2(conn, num)
+
+@app.route('/end_vote/<num>')
+def vote_end(num = '1'):
+    return vote_end_2(conn, num)
+
+@app.route('/close_vote/<num>')
+def vote_close(num = '1'):
+    return vote_close_2(conn, num)
+
+@app.route('/vote')
+def vote():
+    return vote_2(conn)
+
+@app.route('/add_vote', methods=['POST', 'GET'])
+def vote_add():
+    return vote_add_2(conn)
+
 # API
 @app.route('/api/w/<everything:name>', methods=['POST', 'GET'])
 def api_w(name = ''):
@@ -645,7 +674,7 @@ def api_raw(name = ''):
 
 @app.route('/api/version')
 def api_version():
-    return api_version_2(conn, version_list['master']['r_ver'], version_list['master']['c_ver'])
+    return api_version_2(conn, version_list['beta']['r_ver'], version_list['beta']['c_ver'])
 
 @app.route('/api/skin_info')
 @app.route('/api/skin_info/<name>')
@@ -680,7 +709,7 @@ def api_sha224(name = 'test'):
 def api_title_index():
     return api_title_index_2(conn)
 
-@app.route('/api/image/<name>')
+@app.route('/api/image/<everything:name>')
 def api_image_view(name = ''):
     return api_image_view_2(conn, name, app_var)
 

+ 31 - 25
emergency_tool.py

@@ -1,18 +1,11 @@
+# Load
 import time
 from route.tool.func import *
 
-
 # DB
-version_list = json.loads(open('version.json', encoding='utf8').read())
-
-print('Version : ' + version_list['master']['r_ver'])
-print('DB set version : ' + version_list['master']['c_ver'])
-print('Skin set version : ' + version_list['master']['s_ver'])
-print('----')
-
 while 1:
     try:
-        set_data = json.loads(open('data/set.json', encoding='utf8').read())
+        set_data = json.loads(open('data/set.json', encoding = 'utf8').read())
         if not 'db_type' in set_data:
             try:
                 os.remove('data/set.json')
@@ -60,10 +53,10 @@ while 1:
             if new_json[1] == '':
                 new_json[1] = 'data'
 
-            with open('data/set.json', 'w', encoding='utf8') as f:
+            with open('data/set.json', 'w', encoding = 'utf8') as f:
                 f.write('{ "db" : "' + new_json[1] + '", "db_type" : "' + new_json[0] + '" }')
 
-            set_data = json.loads(open('data/set.json', encoding='utf8').read())
+            set_data = json.loads(open('data/set.json', encoding = 'utf8').read())
 
             break
 
@@ -71,37 +64,41 @@ db_data_get(set_data['db_type'])
 
 if set_data['db_type'] == 'mysql':
     try:
-        set_data_mysql = json.loads(open('data/mysql.json', encoding='utf8').read())
+        set_data_mysql = json.loads(open('data/mysql.json', encoding = 'utf8').read())
     except:
-        new_json = ['', '', '']
+        new_json = {}
 
         while 1:
             print('DB user ID : ', end = '')
-            new_json[0] = str(input())
-            if new_json[0] != '':
+            new_json['user'] = str(input())
+            if new_json['user'] != '':
                 break
 
         while 1:
             print('DB password : ', end = '')
-            new_json[1] = str(input())
-            if new_json[1] != '':
+            new_json['password'] = str(input())
+            if new_json['password'] != '':
                 break
                 
         print('DB host (localhost) : ', end = '')
-        new_json[2] = str(input())
-        if new_json[2] == '':
-            new_json[2] == 'localhost'
+        new_json['host'] = str(input())
+        new_json['host'] = 'localhost' if new_json['host'] == '' else new_json['host']
+
+        print('DB port (3306) : ', end = '')
+        new_json['port'] = str(input())
+        new_json['port'] = '3306' if new_json['port'] == '' else new_json['port']
 
-        with open('data/mysql.json', 'w', encoding='utf8') as f:
-            f.write('{ "user" : "' + new_json[0] + '", "password" : "' + new_json[1] + '", "host" : "' + new_json[2] + '" }')
+        with open('data/mysql.json', 'w', encoding = 'utf8') as f:
+            f.write(json.dumps(new_json))
 
-        set_data_mysql = json.loads(open('data/mysql.json', encoding='utf8').read())
+        set_data_mysql = new_json
 
     conn = pymysql.connect(
         host = set_data_mysql['host'] if 'host' in set_data_mysql else 'localhost',
         user = set_data_mysql['user'],
         password = set_data_mysql['password'],
-        charset = 'utf8mb4'
+        charset = 'utf8mb4',
+        port = int(set_data_mysql['port']) if 'port' in set_data_mysql else 3306
     )
     curs = conn.cursor()
 
@@ -134,6 +131,7 @@ print('12. All title count reset')
 print('13. Cache data reset')
 print('14. Delete Main <HEAD>')
 print('15. Give owner')
+print('16. Delete 2FA password')
 
 print('----')
 print('Select : ', end = '')
@@ -284,12 +282,20 @@ elif what_i_do == '13':
     curs.execute(db_change('delete from cache_data'))
 elif what_i_do == '14':
     curs.execute(db_change('delete from other where name = "head"'))
-else:
+elif what_i_do == '15':
     print('----')
     print('User name : ', end = '')
     user_name = input()
 
     curs.execute(db_change("update user set acl = 'owner' where id = ?"), [user_name])
+else:
+    print('----')
+    print('User name : ', end = '')
+    user_name = input()
+
+    curs.execute(db_change('select data from user_set where name = "2fa" and id = ?'), [user_name])
+    if curs.fetchall():
+        curs.execute(db_change("update user_set set data = '' where name = '2fa' and id = ?"), [user_name])
 
 conn.commit()
 

+ 24 - 5
language/en-US.json

@@ -96,11 +96,13 @@
         "icon" : "Icon",
         "view" : "View",
         "content" : "Content",
+        "on" : "On",
         "off" : "Off",
         "unset" : "Unset",
         "extension" : "Extension",
         "empty" : "Empty",
         "domain" : "Domain",
+        "result" : "Result",
         "_comment_1.1_" : "Time",
             "second" : "Second(s)",
             "hour" : "Hour(s)",
@@ -150,15 +152,15 @@
         "user_setting" : "User settings",
         "now_password" : "Current password",
         "new_password" : "New password",
-        "password_confirm" : "Password confirm",
-        "oauth_connection" : "Oauth Connection",
+        "password_confirm" : "Confirm password",
+        "oauth_connection" : "OAuth Connection",
         "password_search" : "Password finder",
         "login_able" : "Loginable",
         "band_ban" : "Range block",
         "band_blocked" : "Range blocked",
         "under_category" : "Sub-category",
         "count" : "Number of Contributions",
-        "alarm" : "Notice(s)",
+        "alarm" : "Notifications(s)",
         "user_document" : "User document",
         "user_head" : "Custom <HEAD>",
         "encryption_method" : "Encryption method",
@@ -190,7 +192,7 @@
         "accept_edit_request" : "Accept edit request",
         "history_add" : "Add history",
         "all_register_num" : "The number of application forms",
-        "replace_move" : "Reversing documents",
+        "replace_move" : "Swaping documents",
         "merge_move" : "Merging documents",
         "add_admin_group" : "Add administrator groups",
         "add_watchlist" : "Add watchlist",
@@ -209,6 +211,9 @@
         "get_sitemap" : "Create or renewal sitemap.xml",
         "simple_check" : "Simple check",
         "add_user" : "Add user",
+        "2fa" : "2FA",
+        "2fa_password" : "2FA password",
+        "2fa_password_change" : "Change 2FA password",
         "_comment_2.1_" : "Filter",
             "_comment_2.1.1_" : "List",
                 "interwiki_list" : "Interwiki(s) list",
@@ -276,6 +281,7 @@
                 "error_401" : "ACL view limited document notice",
                 "error_404" : "Missing document notice",
                 "edit_help" : "Editing textarea phrase",
+                "upload_help" : "File upload phrase",
             "_comment_2.2.4_" : "Google",
                 "recaptcha" : "reCAPTCHA",
                 "smtp_setting" : "Email SMTP setting",
@@ -337,6 +343,7 @@
                 "upload_acl" : "Upload ACL",
                 "edit_req_acl" : "Edit request ACL",
                 "many_upload_acl" : "Upload multiple files ACL",
+                "vote_acl" : "Vote ACL",
         "_comment_2.7_" : "Application list",
             "application_list": "Application list",
             "application_time": "Application time",
@@ -346,6 +353,18 @@
             "approve_or_decline": "Approve or reject",
             "no_applications_now" : "There are no applications.",
             "approval_requirement_disabled": "Approval requirement is disabled now. You can enable this feature on settings",
+        "_comment_2.8_" : "Vote",
+            "vote_list" : "Vote list(s)",
+            "add_vote" : "Add vote",
+            "open_vote" : "Public vote",
+            "not_open_vote" : "Private vote",
+            "close_vote_list" : "Closed vote list(s)",
+            "open_vote_list" : "Open vote list(s)",
+            "vote" : "Vote",
+            "result_vote" : "Result of a vote",
+            "1_line_1_q" : "1 option per line.",
+            "close_vote" : "Close vote",
+            "re_open_vote" : "Reopen vote",
     "_comment_3_" : "Long",
         "application_submitted": "Applicatied successfully for registration",
         "waiting_for_approval": "Application for registration has been made successfully. Please wait for approval by administrators.",
@@ -377,7 +396,7 @@
             "skin_error" : "This skin is not support settings.",
             "same_id_exist_error" : "There are users with the same username.",
             "long_id_error" : "Username must be shorter than 20 characters.",
-            "id_char_error" : "Only Korean letters, alphabets and spaces are allowed for Username.",
+            "id_char_error" : "Only Korean letters, alphabets are allowed for Username.",
             "file_exist_error" : "The file does not exist.",
             "password_error" : "The password is different.",
             "recaptcha_error" : "Go through the reCAPTCHA.",

+ 44 - 26
language/ko-KR.json

@@ -84,7 +84,7 @@
     "all_acl": "모든 사용자",
     "send": "전송",
     "not_sure": "확실하지 않음",
-    "file_name_error": "파일 이름에는 알파벳, 한글, 공백, 밑줄 및 빼기 기호만 사용할 수 있습니다.",
+    "file_name_error": "파일에는 알파벳, 한글, 공백, 밑줄 및 빼기 기호만 사용할 수 있습니다.",
     "pass": "넘기기",
     "recaptcha_error": "'로봇이 아닙니다'를 통해 reCAPTCHA를 통과하세요.",
     "file_capacity_error": "최대 파일 크기 (MB): ",
@@ -145,7 +145,7 @@
     "pinned": "고정",
     "edit_filter_add": "편집 필터 추가",
     "ban_authority": "차단 권한",
-    "file_extension_error": "지정된 확장자의 파일만 업로드 할 수 있습니다.",
+    "file_extension_error": "지정된 확장자의 파일만 올릴 수 있습니다.",
     "host": "호스트",
     "email_text": "이메일 내용",
     "recent": "최근",
@@ -241,7 +241,7 @@
     "file_name": "파일명",
     "close_discussion": "닫힌 토론",
     "language": "언어",
-    "id_char_error": "오직 한글과 알파벳, 공백만 사용 가능합니다.",
+    "id_char_error": "오직 한글과 알파벳만 사용 가능합니다.",
     "id_filter_add": "ID 필터 추가",
     "skin": "스킨",
     "user_head": "사용자 <HEAD>",
@@ -309,7 +309,7 @@
     "many_delete": "문서 대량 삭제",
     "many_delete_help": "한 줄에 문서명을 한 개씩 적어주세요.",
     "content": "내용",
-    "upload_acl": "파일 업로드 ACL",
+    "upload_acl": "파일 올리기 ACL",
     "topic_delete": "토론 삭제",
     "edit_req": "편집 요청",
     "edit_req_check": "편집 요청 검사",
@@ -355,26 +355,44 @@
     "ban_acl": "차단된 사용자 포함",
     "ban_admin_acl": "차단된 사용자 및 관리자",
     "topic_name_change": "토론 제목 변경",
-    "topic_acl_setting" : "토론 ACL 설정",
-    "topic_acl" : "토론 ACL",
-    "main_skin_set" : "기본 스킨 설정",
-    "image_file_list" : "이미지 파일 목록",
-    "many_upload_acl" : "다중 파일 업로드 ACL",
-    "reset_backlink" : "역링크 초기화",
-    "link_in_this" : "이 문서의 링크",
-    "star_doc" : "관심 문서",
-    "add_star_doc" : "관심 문서 추가",
-    "enter_html" : "HTML을 입력하세요.",
-    "exp_edit_conflict" : "편집 충돌 발생",
-    "backup_where" : "백업 위치",
-    "empty" : "빈칸",
-    "email_send_error" : "이메일 전송이 실패했습니다.",
-    "get_sitemap" : "sitemap.xml 만들거나 갱신하기",
-    "same_ip_exist" : "동일한 아이피가 존재합니다.",
-    "restart_fail_error" : "재시작이 실패했습니다. 수동 재시작을 이용해주세요.",
-    "domain" : "도메인",
-    "before_acl" : "이 문서를 이전에 편집한 적 있는 사람만",
-    "30_day_acl" : "가입 후 30일이 지난 가입자만",
-    "simple_check" : "간편 검사",
-    "add_user" : "계정 추가"
+    "topic_acl_setting": "토론 ACL 설정",
+    "topic_acl": "토론 ACL",
+    "main_skin_set": "기본 스킨 설정",
+    "image_file_list": "이미지 파일 목록",
+    "many_upload_acl": "다중 파일 올리기 ACL",
+    "reset_backlink": "역링크 초기화",
+    "link_in_this": "이 문서의 링크",
+    "star_doc": "관심 문서",
+    "add_star_doc": "관심 문서 추가",
+    "enter_html": "HTML을 입력하세요.",
+    "exp_edit_conflict": "편집 충돌 발생",
+    "backup_where": "백업 위치",
+    "empty": "빈칸",
+    "email_send_error": "이메일 전송이 실패했습니다.",
+    "get_sitemap": "sitemap.xml 만들거나 갱신하기",
+    "same_ip_exist": "동일한 아이피가 존재합니다.",
+    "restart_fail_error": "재시작이 실패했습니다. 수동 재시작을 이용해주세요.",
+    "domain": "도메인",
+    "before_acl": "이 문서를 이전에 편집한 적 있는 사람만",
+    "30_day_acl": "가입 후 30일이 지난 가입자만",
+    "simple_check": "간편 검사",
+    "add_user": "계정 추가",
+    "result": "결과",
+    "vote_list": "투표 목록",
+    "add_vote": "투표 추가",
+    "open_vote": "공개 투표",
+    "not_open_vote": "비밀 투표",
+    "close_vote_list": "닫힌 투표 목록",
+    "vote": "투표",
+    "result_vote": "투표 결과",
+    "1_line_1_q": "1줄당 1개의 선택지를 쓰세요.",
+    "open_vote_list" : "열린 투표 목록",
+    "close_vote" : "투표 닫기",
+    "re_open_vote" : "투표 다시 열기",
+    "on" : "켜기",
+    "2fa" : "2차 인증",
+    "2fa_password" : "2차 비밀번호",
+    "2fa_password_change" : "2차 비밀번호 변경",
+    "vote_acl" : "투표 ACL",
+    "upload_help" : "파일 올리기 문구"
 }

+ 16 - 45
readme-en.md

@@ -1,9 +1,9 @@
-openNAMU
-====
+[(en-US)](./readme-en.md) | [(ko-KR)](./readme.md)
+# openNAMU
 [![Up to Python 3.5](https://img.shields.io/badge/python->=%203.5-blue.svg)](https://python.org)
 [![LICENSE](https://img.shields.io/badge/license-BSD%203--Clause-lightgrey.svg)](./LICENSE)
 
-![](https://raw.githubusercontent.com/2du/openNAMU/master/.github/logo.png)
+![](https://raw.githubusercontent.com/2du/openNAMU/beta/.github/logo.png)
 
 openNAMU is a Python-based wiki engine. You can use openNAMU by installing Python and its dependency modules, and you can modify the code yourself to create more specialized wikis.
 
@@ -12,71 +12,42 @@ openNAMU is a Python-based wiki engine. You can use openNAMU by installing Pytho
  * [Clone](#clone)
  * [Contribute](#contribute)
  * [License](#license)
- * [Authors](#authors)
  * [Etc.](#etc)
 
-# Getting Started
+## Getting Started
 openNAMU is based upon Python, and it requires a Python environment.
 
-### Download Releases
-Download the [release version of openNAMU](https://github.com/2du/openNAMU/releases), and unzip the file. It is also possible to download releases by [cloning this repository](#Clone).
+[Here](https://2du.pythonanywhere.com/w/en%3AInstall) you can see the guide.
 
-### Install Modules
-Windows
-```
-pip install -r requirements.txt
-```
-
-Linux
-```
-pip3 install -r requirements.txt
-```
-## Launching Application
-Windows
-```
-python app.py
-```
-
-Linux
-```
-python3 app.py
-```
-
-## Publishing Application
-
-# Clone
+## Clone
 You can clone this repository by entering the following command at the terminal (command prompt):
-## Stable
  * `git clone -b stable https://github.com/2du/openNAMU.git`
+ * `git clone -b beta https://github.com/2du/openNAMU.git`
+ * `git clone -b dev https://github.com/2du/openNAMU.git`
 
-## Beta
- * `git clone -b master https://github.com/2du/openNAMU.git`
-
-# Contribute
-openNAMU may have some untested bugs. Your use of openNAMU and bug discovery will help develop openNAMU.
-[[Create Issues]](https://github.com/2du/openNAMU/issues/new)
+## Contribute
+openNAMU may have some untested bugs. Your use of openNAMU and bug discovery will help develop openNAMU. [(Create Issues)](https://github.com/2du/openNAMU/issues/new)
 
-openNAMU is a complete open source project. Add new features and create pull requests.
-[[Create Pull Requests]](https://github.com/2du/openNAMU/compare)
+openNAMU is a complete open source project. Add new features and create pull requests. [(Create Pull Requests)](https://github.com/2du/openNAMU/compare)
 
-# Lisence
+## Lisence
 openNAMU is protected by [BSD 3-Clause License](./LICNESE). Please refer to the documentation for details.
 
-## External Projects
+### External Projects
  * Quotes icon [Dave Gandy](http://www.flaticon.com/free-icon/quote-left_25672)
  * Syntax highlighting [highlightjs](https://highlightjs.org/)
  * Numerical expression [MathJax](https://www.mathjax.org/)
  * Handling Keyboard Shortcuts [shortcut.js](http://www.openjs.com/scripts/events/keyboard_shortcuts/)
 
-# Authors
+### Authors
  * [Reference](https://github.com/2DU/openNAMU/graphs/contributors)
 
-## Special Thanks
+### Special Thanks
  * [Team Croatia](https://github.com/TeamCroatia)
  * Basix
  * Efrit
  * Other chat rooms
 
-# Etc.
+## Etc.
  * Owner rights are granted to the first registor.
  * [Test Server](http://2du.pythonanywhere.com)

+ 13 - 15
readme.md

@@ -1,32 +1,30 @@
-오픈나무
-====
+[(en-US)](./readme-en.md) | [(ko-KR)](./readme.md)
+# 오픈나무
 [![Python 3.5 이상](https://img.shields.io/badge/python->=%203.5-blue.svg)](https://python.org)
 [![라이선스](https://img.shields.io/badge/license-BSD%203--Clause-lightgrey.svg)](./LICENSE)
 
-![](https://raw.githubusercontent.com/2du/openNAMU/master/.github/logo.png)
+![](https://raw.githubusercontent.com/2du/openNAMU/beta/.github/logo.png)
 
 오픈나무는 파이썬 기반의 위키 엔진입니다. 파이썬과 그 의존성 모듈만 설치하면 사용할 수 있으며, 코드를 직접 수정하여 좀 더 주제에 특화된 위키를 만들 수 있습니다.
 
- * [(README for english)](./readme-en.md)
 ### 목차
+ * [시작하기](#시작하기)
  * [클론](#클론)
  * [기여](#기여)
  * [라이선스](#라이선스)
- * [기여자 목록](#기여자-목록)
+ * [지원 문법](#지원-문법)
  * [기타](#기타)
 
 ## 시작하기
 오픈나무는 파이썬 환경에서 동작하는 파이썬 애플리케이션으로, 파이썬 환경을 필요로 합니다.
 
-쉬운 오픈나무 설치를 위해 오픈나무 가이드를 따로 생성해두었으며, [이곳](http://2du.pythonanywhere.com)에서 확인하실 수 있습니다.
+쉬운 오픈나무 설치를 위해 오픈나무 가이드를 따로 생성해두었으며, [이곳](https://2du.pythonanywhere.com/w/%EC%84%A4%EC%B9%98%EB%B2%95)에서 확인하실 수 있습니다.
 
 ## 클론
 아래 명령을 터미널(명령 프롬프트)에 입력하여 본 리포지토리를 클론할 수 있습니다.
-### 일반
  * `git clone -b stable https://github.com/2du/openNAMU.git`
-
-### 개발판
- * `git clone -b master https://github.com/2du/openNAMU.git`
+ * `git clone -b beta https://github.com/2du/openNAMU.git`
+ * `git clone -b dev https://github.com/2du/openNAMU.git`
 
 ## 기여
 오픈나무에는 검증되지 않은 몇가지 버그가 존재할 수 있습니다. 당신의 오픈나무 사용과 버그 발견은 오픈나무의 발전을 돕습니다. [(이슈 생성하기)](https://github.com/2du/openNAMU/issues/new)
@@ -42,11 +40,7 @@
  * Numerical expression - [MathJax](https://www.mathjax.org/)
  * Handling Keyboard Shortcuts [shortcut.js](http://www.openjs.com/scripts/events/keyboard_shortcuts/)
 
-## 지원 문법
- * 나무마크 (NamuMark)
- * 마크다운 (Markdown) (예정)
-
-## 기여자 목록
+### 기여자 목록
  * [참고](https://github.com/2DU/openNAMU/graphs/contributors)
 
 ### 도움을 주신 분들
@@ -55,6 +49,10 @@
  * Efrit
  * 기타 채팅방 사람들
 
+## 지원 문법
+ * 나무마크 (NamuMark)
+ * 마크다운 (Markdown) (예정)
+
 ## 기타
  * 첫 가입자에게 소유자 권한이 부여됩니다.
  * [테스트 서버](http://2du.pythonanywhere.com)

+ 1 - 1
requirements.txt

@@ -1,6 +1,6 @@
-tornado
 flask
 flask-Reggie
+tornado
 requests
 pymysql
 diff-match-patch

+ 1 - 1
route/api_markup.py

@@ -5,7 +5,7 @@ def api_markup_2(conn):
 
     curs.execute(db_change('select data from other where name = "markup"'))
     rep_data = curs.fetchall()
-    if rep_data[0][0] != '':
+    if rep_data and rep_data[0][0] != '':
         return flask.jsonify({ "markup" : rep_data[0][0] })
     else:
         return flask.jsonify({})

+ 2 - 4
route/api_raw.py

@@ -10,7 +10,5 @@ def api_raw_2(conn, name):
             json_data = { "title" : name, "data" : render_set(title = name, data = data[0][0], s_data = 1) }
 
             return flask.jsonify(json_data)
-        else:
-            return flask.jsonify({})
-    else:
-        return flask.jsonify({})
+        
+    return flask.jsonify({})

+ 16 - 7
route/api_recent_change.py

@@ -5,14 +5,23 @@ def api_recent_change_2(conn):
 
     num = int(number_check(flask.request.args.get('num', '10')))
     num = 50 if (1 if not num > 0 else num) > 50 else num
-
+    repeat_ok = flask.request.args.get('repeat', '1')
     data_list = []
+    admin = admin_check(6)
+    get_title = ''
+
     curs.execute(db_change('select id, title from rc order by date desc'))
     for i in curs.fetchall():
-        curs.execute(db_change('select id, title, date, ip, send, leng, hide from history where id = ? and title = ?'), i)
-        data_list += curs.fetchall()
+        if repeat_ok == '1' or i[1] != get_title:
+            get_title = i[1]
+            curs.execute(db_change('select id, title, date, ip, send, leng, hide from history where id = ? and title = ?'), i)
+            get_data = curs.fetchall()
+            if get_data:
+                if get_data[0][6] == '' or admin == 1:
+                    data_list += get_data
+                else:
+                    data_list += [['', '', '', '', '', '', get_data[0][6]]]
+            else:
+                data_list += [['', '', '', '', '', '', '']]
         
-    if data_list:
-        return flask.jsonify(data_list)
-    else:
-        return flask.jsonify({})
+    return flask.jsonify(data_list if data_list else {}) 

+ 7 - 7
route/api_version.py

@@ -8,24 +8,24 @@ def api_version_2(conn, r_ver, c_ver):
 
     curs.execute(db_change('select data from other where name = "update"'))
     up_data = curs.fetchall()
-    if up_data:
-        up_data = up_data[0][0]
-    else:
-        up_data = 'stable'
+    up_data = up_data[0][0] if up_data and up_data[0][0] in ['stable', 'beta', 'dev'] else 'stable'
 
     try:
-        data = urllib.request.urlopen('https://raw.githubusercontent.com/2du/openNAMU/master/version.json')
+        data = urllib.request.urlopen('https://raw.githubusercontent.com/2du/openNAMU/' + up_data + '/version.json')
     except:
         data = None
 
     if data and data.getcode() == 200:
         try:
             json_data = json.loads(data.read().decode())
-            if up_data in json_data:
-                n_ver = json_data[up_data]['r_ver']
         except:
             pass
 
+        if 'beta' in json_data:
+            n_ver = json_data['beta']['r_ver']
+        elif 'master' in json_data:
+            n_ver = json_data['master']['r_ver']
+
     json_data = { "version" : r_ver, "db_version" : c_ver, "lastest_version" : n_ver  }
 
     return flask.jsonify(json_data)

+ 1 - 5
route/api_w.py

@@ -20,11 +20,7 @@ def api_w_2(conn, name):
                 data = curs.fetchall()
                 if data:
                     if flask.request.args.get('include', 'include_1'):
-                        include_re = re.compile(r'\[include\(((?:(?!\)\]).)+)\)\]', re.I)
-                        category_re = re.compile(r'\[\[(?:(?:category|분류):(?:(?!\[\[|\]\]).)+)\]\]', re.I)
-
-                        json_data = include_re.sub('', data[0][0])
-                        json_data = category_re.sub('', json_data)
+                        json_data = data[0][0]
 
                         get_all_change_1 = [('@' + i[0] + '@', i[1]) for i in re.findall(r'@([^=]+)=([^@]+)@', json_data)]
                         json_data = re.sub(r'@(?P<in>[^=]+)=([^@]+)@', '@\g<in>@', json_data)

+ 6 - 1
route/edit.py

@@ -19,7 +19,12 @@ def edit_2(conn, name):
     ip = ip_check()
     section = flask.request.args.get('section', None)
     if section:
-        section = int(number_check(section))
+        curs.execute(db_change("select data from other where name = 'markup'"))
+        markup = curs.fetchall()
+        if markup[0][0] == 'namumark':
+            section = int(number_check(section))
+        else:
+            return redirect('/edit/' + url_pas(name))
 
     if acl_check(name) == 1:
         return re_error('/ban')

+ 9 - 9
route/edit_delete.py

@@ -61,14 +61,14 @@ def edit_delete_2(conn, name, app_var):
 
         return easy_minify(flask.render_template(skin_check(),
             imp = [name, wiki_set(), custom(), other2(['(' + load_lang('delete') + ')', 0])],
-            data =  '''
-                    <form method="post">
-                        ''' + ip_warring() + '''
-                        <input placeholder="''' + load_lang('why') + '''" name="send" type="text">
-                        <hr class=\"main_hr\">
-                        ''' + captcha_get() + '''
-                        <button type="submit">''' + load_lang('delete') + '''</button>
-                    </form>
-                    ''',
+            data = '''
+                <form method="post">
+                    ''' + ip_warring() + '''
+                    <input placeholder="''' + load_lang('why') + '''" name="send" type="text">
+                    <hr class=\"main_hr\">
+                    ''' + captcha_get() + '''
+                    <button type="submit">''' + load_lang('delete') + '''</button>
+                </form>
+            ''',
             menu = [['w/' + url_pas(name), load_lang('return')]]
         ))     

+ 3 - 37
route/edit_many_delete.py

@@ -1,4 +1,5 @@
 from .tool.func import *
+from . import edit_delete
 
 def edit_many_delete_2(conn, app_var):
     curs = conn.cursor()
@@ -10,44 +11,9 @@ def edit_many_delete_2(conn, app_var):
     if flask.request.method == 'POST':
         all_title = re.findall(r'([^\n]+)\n', flask.request.form.get('content', '').replace('\r\n', '\n') + '\n')
         for name in all_title:
-            curs.execute(db_change("select data from data where title = ?"), [name])
-            data = curs.fetchall()
-            if data:
-                today = get_time()
-                leng = '-' + str(len(data[0][0]))
+            edit_delete.edit_delete_2(conn, name, app_var)
 
-                history_plus(
-                    name,
-                    '',
-                    today,
-                    ip,
-                    flask.request.form.get('send', ''),
-                    leng,
-                    'delete'
-                )
-
-                curs.execute(db_change("select title, link from back where title = ? and not type = 'cat' and not type = 'no'"), [name])
-                for data in curs.fetchall():
-                    curs.execute(db_change("insert into back (title, link, type) values (?, ?, 'no')"), [data[0], data[1]])
-
-                curs.execute(db_change("delete from back where link = ?"), [name])
-                curs.execute(db_change("delete from data where title = ?"), [name])
-                conn.commit()
-
-            file_check = re.search(r'^file:(.+)\.(.+)$', name)
-            if file_check:
-                file_check = file_check.groups()
-                file_directory = os.path.join(
-                    app_var['path_data_image'], 
-                    sha224_replace(file_check[0]) + '.' + file_check[1]
-                )
-                if os.path.exists(file_directory):
-                    os.remove(file_directory)
-
-            curs.execute(db_change('select data from other where name = "count_all_title"'))
-            curs.execute(db_change("update other set data = ? where name = 'count_all_title'"), [str(int(curs.fetchall()[0][0]) - 1)])
-
-        return redirect('/recent_changes')
+            return redirect('/recent_changes')
     else:
         return easy_minify(flask.render_template(skin_check(),
             imp = [load_lang('many_delete'), wiki_set(), custom(), other2([0, 0])],

+ 4 - 2
route/edit_move.py

@@ -7,6 +7,10 @@ def edit_move_2(conn, name):
         return re_error('/ban')
 
     if flask.request.method == 'POST':
+        move_title = flask.request.form.get('title', 'test')
+        if acl_check(move_title) == 1:
+            return re_error('/ban')
+
         if captcha_post(flask.request.form.get('g-recaptcha-response', flask.request.form.get('g-recaptcha', ''))) == 1:
             return re_error('/error/13')
         else:
@@ -15,8 +19,6 @@ def edit_move_2(conn, name):
         if slow_edit_check() == 1:
             return re_error('/error/24')
 
-        move_title = flask.request.form.get('title', 'test')
-
         curs.execute(db_change("select title from history where title = ?"), [move_title])
         if curs.fetchall():
             if flask.request.form.get('move_option', 'normal') == 'merge' and admin_check(None, 'merge documents') == 1:

+ 13 - 13
route/func_upload.py

@@ -116,35 +116,35 @@ def func_upload_2(conn, app_var):
 
         return redirect('/w/file:' + name)
     else:
-        license_list = '''
-            <option value="direct_input">''' + load_lang('direct_input') + '''</option>
-        '''
+        license_list = '<option value="direct_input">' + load_lang('direct_input') + '</option>'
 
         curs.execute(db_change("select html from html_filter where kind = 'image_license'"))
         db_data = curs.fetchall()
-        for i in db_data:
-            license_list += '''
-                <option value="''' + i[0] + '''">''' + i[0] + '''</option>
-            '''
+        license_list += ''.join(['<option value="' + i[0] + '">' + i[0] + '</option>' for i in db_data])
+
+        curs.execute(db_change("select data from other where name = 'upload_help'"))
+        db_data = curs.fetchall()
+        upload_help = ('<hr class="main_hr">' + db_data[0][0]) if db_data and db_data[0][0] != '' else ''
 
         return easy_minify(flask.render_template(skin_check(),
             imp = [load_lang('upload'), wiki_set(), custom(), other2([0, 0])],
             data = '''
                 <a href="/file_filter">(''' + load_lang('file_filter_list') + ''')</a>
-                <hr class=\"main_hr\">
+                ''' + upload_help + '''
+                <hr class="main_hr">
                 ''' + load_lang('max_file_size') + ''' : ''' + wiki_set(3) + '''MB
-                <hr class=\"main_hr\">
+                <hr class="main_hr">
                 <form method="post" enctype="multipart/form-data" accept-charset="utf8">
                     <input multiple="multiple" type="file" name="f_data[]">
-                    <hr class=\"main_hr\">
+                    <hr class="main_hr">
                     <input placeholder="''' + load_lang('file_name') + '''" name="f_name" value="''' + flask.request.args.get('name', '') + '''">
-                    <hr class=\"main_hr\">
+                    <hr class="main_hr">
                     <select name="f_lice_sel">
                         ''' + license_list + '''
                     </select>
-                    <hr class=\"main_hr\">
+                    <hr class="main_hr">
                     <textarea rows="10" placeholder="''' + load_lang('other') + '''" name="f_lice"></textarea>
-                    <hr class=\"main_hr\">
+                    <hr class="main_hr">
                     ''' + captcha_get() + '''
                     <button id="save" type="submit">''' + load_lang('save') + '''</button>
                 </form>

+ 11 - 11
route/give_acl.py

@@ -142,17 +142,17 @@ def give_acl_2(conn, name):
                 </ul>
             '''
 
-            if check_ok == '':
-                if acl_data:
-                    data += '' + \
-                        '<hr class="main_hr">' + \
-                        '<input value="' + html.escape(acl_data[0][1]) + '" placeholder="' + load_lang('why') + '" name="why" type="text" ' + check_ok + '>' + \
-                    ''
-                else:
-                    data += '' + \
-                        '<hr class="main_hr">' + \
-                        '<input placeholder="' + load_lang('why') + '" name="why" type="text" ' + check_ok + '>' + \
-                    ''
+            if acl_data:
+                data += '' + \
+                    '<hr class="main_hr">' + \
+                    '<input value="' + html.escape(acl_data[0][1]) + '" placeholder="' + load_lang('why') + '" name="why" type="text" ' + check_ok + '>' + \
+                ''
+            else:
+                data += '' + \
+                    '<hr class="main_hr">' + \
+                    '<input placeholder="' + load_lang('why') + '" name="why" type="text" ' + check_ok + '>' + \
+                ''
+                
 
         return easy_minify(flask.render_template(skin_check(),
             imp = [name, wiki_set(), custom(), other2(['(' + load_lang('acl') + ')', 0])],

+ 2 - 1
route/give_history_add.py

@@ -20,7 +20,8 @@ def give_history_add_2(conn, name):
             today,
             'Add:' + flask.request.form.get('get_ip', ''),
             flask.request.form.get('send', ''),
-            leng
+            leng,
+            mode = 'add'
         )
 
         conn.commit()

+ 11 - 6
route/give_user_ban.py

@@ -19,11 +19,12 @@ def give_user_ban_2(conn, name):
         return re_error('/ban')
 
     if flask.request.method == 'POST':
-        name = name if name else flask.request.form.get('name', 'test')
-
         end = flask.request.form.get('second', '0')
         end = end if end else '0'
+        name = name if name else flask.request.form.get('name', 'test')
         regex_get = flask.request.form.get('regex', None)
+        login = flask.request.form.get('login', '')
+        why = flask.request.form.get('why', '')
 
         if regex_get or band != '':
             type_d = 'regex' if regex_get else band
@@ -35,14 +36,18 @@ def give_user_ban_2(conn, name):
         else:
             type_d = None
 
-        if admin_check(1, 'ban' + (' ' + type_d if type_d else '') + ' (' + name + ')') != 1:
-            return re_error('/error/3')
+        if type_d:
+            if admin_check(None, 'ban' + (' ' + type_d if type_d else '') + ' (' + name + ')') != 1:
+                return re_error('/error/3')
+        else:
+            if admin_check(1, 'ban (' + name + ')') != 1:
+                return re_error('/error/3')
 
         ban_insert(
             name,
             end,
-            flask.request.form.get('why', ''),
-            flask.request.form.get('login', ''),
+            why,
+            login,
             ip_check(),
             type_d
         )

+ 3 - 3
route/inter_wiki.py

@@ -12,7 +12,7 @@ def inter_wiki_2(conn, tools):
         title = load_lang('interwiki_list')
         div = ''
 
-        curs.execute(db_change('select title, link from inter'))
+        curs.execute(db_change("select html, plus from html_filter where kind = 'inter_wiki'"))
     elif tools == 'email_filter':
         del_link = 'del_email_filter'
         plus_link = 'plus_email_filter'
@@ -29,11 +29,11 @@ def inter_wiki_2(conn, tools):
         curs.execute(db_change("select html from html_filter where kind = 'name'"))
     elif tools == 'edit_filter':
         del_link = 'del_edit_filter'
-        plus_link = 'manager/9'
+        plus_link = 'plus_edit_filter'
         title = load_lang('edit_filter_list')
         div = ''
 
-        curs.execute(db_change("select name from filter"))
+        curs.execute(db_change("select html from html_filter where kind = 'regex_filter'"))
     elif tools == 'file_filter':
         del_link = 'del_file_filter'
         plus_link = 'plus_file_filter'

+ 2 - 2
route/inter_wiki_del.py

@@ -5,9 +5,9 @@ def inter_wiki_del_2(conn, tools, name):
 
     if admin_check(None, tools) == 1:
         if tools == 'del_inter_wiki':
-            curs.execute(db_change("delete from inter where title = ?"), [name])
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'inter_wiki'"), [name])
         elif tools == 'del_edit_filter':
-            curs.execute(db_change("delete from filter where name = ?"), [name])
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'regex_filter'"), [name])
         elif tools == 'del_name_filter':
             curs.execute(db_change("delete from html_filter where html = ? and kind = 'name'"), [name])
         elif tools == 'del_file_filter':

+ 11 - 8
route/inter_wiki_plus.py

@@ -6,9 +6,12 @@ def inter_wiki_plus_2(conn, tools, name):
     if flask.request.method == 'POST':
         if tools == 'plus_inter_wiki':
             if name:
-                curs.execute(db_change("delete from inter where title = ?"), [name])
+                curs.execute(db_change("delete from html_filter where html = ? and kind = 'inter_wiki'"), [name])
 
-            curs.execute(db_change('insert into inter (title, link, icon) values (?, ?, ?)'), [
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'inter_wiki'"), [
+                flask.request.form.get('title', 'test')
+            ])
+            curs.execute(db_change('insert into html_filter (html, plus, plus_t, kind) values (?, ?, ?, "inter_wiki")'), [
                 flask.request.form.get('title', 'test'),
                 flask.request.form.get('link', 'test'),
                 flask.request.form.get('icon', '')
@@ -16,7 +19,7 @@ def inter_wiki_plus_2(conn, tools, name):
 
             admin_check(None, 'inter_wiki_plus')
         elif tools == 'plus_edit_filter':
-            if admin_check(1, 'edit_filter edit') != 1:
+            if admin_check(None, 'edit_filter edit') != 1:
                 return re_error('/error/3')
 
             if flask.request.form.get('second', '0') == '0':
@@ -27,8 +30,8 @@ def inter_wiki_plus_2(conn, tools, name):
             try:
                 re.compile(flask.request.form.get('content', 'test'))
 
-                curs.execute(db_change("delete from filter where name = ?"), [name])
-                curs.execute(db_change("insert into filter (name, regex, sub) values (?, ?, ?)"), [
+                curs.execute(db_change("delete from html_filter where html = ? and kind = 'regex_filter'"), [name])
+                curs.execute(db_change("insert into html_filter (html, plus, plus_t, kind) values (?, ?, ?, 'regex_filter')"), [
                     name,
                     flask.request.form.get('content', 'test'),
                     end
@@ -91,14 +94,14 @@ def inter_wiki_plus_2(conn, tools, name):
         return redirect('/' + re.sub(r'^plus_', '', tools))
     else:
         get_sub = 0
-        if admin_check(1) != 1:
+        if admin_check() != 1:
             stat = 'disabled'
         else:
             stat = ''
 
         if tools == 'plus_inter_wiki':
             if name:
-                curs.execute(db_change("select title, link, icon from inter where title = ?"), [name])
+                curs.execute(db_change("select html, plus, plus_t from html_filter where html = ? and kind = 'inter_wiki'"), [name])
                 exist = curs.fetchall()
                 if exist:
                     value = exist[0]
@@ -122,7 +125,7 @@ def inter_wiki_plus_2(conn, tools, name):
                 <input value="''' + html.escape(value[2]) + '''" type="text" name="icon">
             '''
         elif tools == 'plus_edit_filter':
-            curs.execute(db_change("select regex, sub from filter where name = ?"), [name])
+            curs.execute(db_change("select plus, plus_t from html_filter where html = ? and kind = 'regex_filter'"), [name])
             exist = curs.fetchall()
             if exist:
                 textarea = exist[0][0]

+ 27 - 14
route/login.py

@@ -17,8 +17,9 @@ def login_2(conn):
             captcha_post('', 0)
 
         agent = flask.request.headers.get('User-Agent')
+        user_id = flask.request.form.get('id', '')
 
-        curs.execute(db_change("select pw, encode from user where id = ?"), [flask.request.form.get('id', None)])
+        curs.execute(db_change("select pw, encode from user where id = ?"), [user_id])
         user = curs.fetchall()
         if not user:
             return re_error('/error/2')
@@ -27,28 +28,40 @@ def login_2(conn):
             flask.request.form.get('pw', ''),
             user[0][0],
             user[0][1],
-            flask.request.form.get('id', None)
+            user_id
         )
         if pw_check_d != 1:
             return re_error('/error/10')
 
-        flask.session['state'] = 1
-        flask.session['id'] = flask.request.form.get('id', None)
+        curs.execute(db_change('select data from user_set where name = "2fa" and id = ?'), [user_id])
+        fa_data = curs.fetchall()
+        if fa_data and fa_data[0][0] != '':
+            curs.execute(db_change("select css from custom where user = ?"), [user_id])
+            css_data = curs.fetchall()
+            flask.session['b_head'] = css_data[0][0] if css_data else ''
+            flask.session['b_id'] = user_id
 
-        curs.execute(db_change("select css from custom where user = ?"), [flask.request.form.get('id', None)])
-        css_data = curs.fetchall()
-        if css_data:
-            flask.session['head'] = css_data[0][0]
+            return redirect('/2fa_login')
         else:
-            flask.session['head'] = ''
+            curs.execute(db_change("select css from custom where user = ?"), [user_id])
+            css_data = curs.fetchall()
+            flask.session['head'] = css_data[0][0] if css_data else ''
+            flask.session['id'] = user_id
 
-        curs.execute(db_change("insert into ua_d (name, ip, ua, today, sub) values (?, ?, ?, ?, '')"), [flask.request.form.get('id', None), ip_check(1), agent, get_time()])
+            curs.execute(db_change("insert into ua_d (name, ip, ua, today, sub) values (?, ?, ?, ?, '')"), [
+                user_id, 
+                ip, 
+                agent, 
+                get_time()
+            ])
+            conn.commit()
 
-        conn.commit()
-
-        return redirect('/user')
+            return redirect('/user')
     else:
-        http_warring = '<hr class=\"main_hr\"><span>' + load_lang('http_warring') + '</span>'
+        http_warring = '' + \
+            '<hr class="main_hr">' + \
+            '<span>' + load_lang('http_warring') + '</span>' + \
+        ''
 
         return easy_minify(flask.render_template(skin_check(),
             imp = [load_lang('login'), wiki_set(), custom(), other2([0, 0])],

+ 74 - 0
route/login_2fa.py

@@ -0,0 +1,74 @@
+from .tool.func import *
+
+def login_2fa_2(conn):
+    curs = conn.cursor()
+
+    if not (flask.session and 'b_id' in flask.session):
+        return redirect('/user')
+
+    ip = ip_check()
+    if ip_or_user(ip) == 0:
+        return redirect('/user')
+
+    if ban_check(tool = 'login') == 1:
+        return re_error('/ban')
+
+    if flask.request.method == 'POST':
+        if captcha_post(flask.request.form.get('g-recaptcha-response', flask.request.form.get('g-recaptcha', ''))) == 1:
+            return re_error('/error/13')
+        else:
+            captcha_post('', 0)
+
+        agent = flask.request.headers.get('User-Agent')
+        user_id = flask.session['b_id']
+
+        curs.execute(db_change('select data from user_set where name = "2fa_pw" and id = ?'), [user_id])
+        user_1 = curs.fetchall()
+        if user_1:
+            curs.execute(db_change('select data from user_set where name = "2fa_pw_encode" and id = ?'), [user_id])
+            user_1 = user_1[0][0]
+            user_2 = curs.fetchall()[0][0]
+
+            pw_check_d = pw_check(
+                flask.request.form.get('pw', ''),
+                user_1,
+                user_2,
+                user_id
+            )
+            if pw_check_d != 1:
+                return re_error('/error/10')
+
+        flask.session['head'] = flask.session['b_head']
+        flask.session['id'] = user_id
+
+        curs.execute(db_change("insert into ua_d (name, ip, ua, today, sub) values (?, ?, ?, ?, '')"), [
+            user_id, 
+            ip, 
+            agent, 
+            get_time()
+        ])
+        conn.commit()
+
+        flask.session.pop('b_id', None)
+        flask.session.pop('b_head', None)
+
+        return redirect('/user')
+    else:
+        http_warring = '' + \
+            '<hr class="main_hr">' + \
+            '<span>' + load_lang('http_warring') + '</span>' + \
+        ''
+
+        return easy_minify(flask.render_template(skin_check(),
+            imp = [load_lang('login'), wiki_set(), custom(), other2([0, 0])],
+            data =  '''
+                    <form method="post">
+                        <input placeholder="''' + load_lang('2fa_password') + '''" name="pw" type="password">
+                        <hr class=\"main_hr\">
+                        ''' + captcha_get() + '''
+                        <button type="submit">''' + load_lang('login') + '''</button>
+                        ''' + http_warring + '''
+                    </form>
+                    ''',
+            menu = [['user', load_lang('return')]]
+        ))

+ 6 - 2
route/login_check_key.py

@@ -3,6 +3,7 @@ from .tool.func import *
 def login_check_key_2(conn, tool):
     curs = conn.cursor()
 
+    # 난잡한 코드 정리 필요
     if flask.request.method == 'POST':
         if tool == 'check_pass_key':
             if 'c_id' in flask.session and flask.session['c_key'] == flask.request.form.get('key', None):
@@ -20,10 +21,14 @@ def login_check_key_2(conn, tool):
                 curs.execute(db_change('select data from other where name = "reset_user_text"'))
                 sql_d = curs.fetchall()
                 if sql_d and sql_d[0][0] != '':
-                    b_text = sql_d[0][0] + '<hr class=\"main_hr\">'
+                    b_text = sql_d[0][0] + '<hr class="main_hr">'
                 else:
                     b_text = ''
 
+                curs.execute(db_change('select data from user_set where name = "2fa" and id = ?'), [d_id])
+                if curs.fetchall():
+                    curs.execute(db_change("update user_set set data = '' where name = '2fa' and id = ?"), [d_id])
+
                 return easy_minify(flask.render_template(skin_check(),
                     imp = [load_lang('reset_user_ok'), wiki_set(), custom(), other2([0, 0])],
                     data = b_text + load_lang('id') + ' : ' + d_id + '<br>' + load_lang('password') + ' : ' + pw,
@@ -101,7 +106,6 @@ def login_check_key_2(conn, tool):
                         get_time()
                     ])
 
-                    flask.session['state'] = 1
                     flask.session['id'] = flask.session['c_id']
                     flask.session['head'] = ''
 

+ 1 - 2
route/login_register.py

@@ -29,7 +29,7 @@ def login_register_2(conn):
         if flask.request.form.get('pw', None) != flask.request.form.get('pw2', None):
             return re_error('/error/20')
 
-        if re.search(r'(?:[^A-Za-zㄱ-힣0-9 ])', flask.request.form.get('id', None)):
+        if re.search(r'(?:[^A-Za-zㄱ-힣0-9])', flask.request.form.get('id', None)):
             return re_error('/error/8')
 
         curs.execute(db_change('select html from html_filter where kind = "name"'))
@@ -116,7 +116,6 @@ def login_register_2(conn):
 
             curs.execute(db_change("insert into ua_d (name, ip, ua, today, sub) values (?, ?, ?, ?, '')"), [flask.request.form.get('id', None), ip, agent, get_time()])
 
-            flask.session['state'] = 1
             flask.session['id'] = flask.request.form.get('id', None)
             flask.session['head'] = ''
 

+ 2 - 4
route/main_file.py

@@ -18,7 +18,5 @@ def main_file_2(conn, data):
                 return flask.send_from_directory('./', data, mimetype = 'text/plain')
             else:
                 return flask.send_from_directory('./', data, mimetype = 'text/xml')
-        else:
-            return main_error_404.main_error_404_2(conn)
-    else:
-        return main_error_404.main_error_404_2(conn)
+
+    return main_error_404.main_error_404_2(conn)

+ 5 - 3
route/main_image_view.py

@@ -1,12 +1,14 @@
 from .tool.func import *
+from . import main_error_404
 
 def main_image_view_2(conn, name, app_var):
     curs = conn.cursor()
 
-    if os.path.exists(os.path.join(app_var['path_data_image'], name)):
+    mime_type = re.search(r'([^.]+)$', name)
+    if mime_type:
         return flask.send_from_directory(
             './' + app_var['path_data_image'], name, 
-            mimetype = 'image/' + re.search(r'\.([^\.]+)$', name).group(1)
+            mimetype = 'image/' + mime_type.group(1).lower()
         )
     else:
-        return redirect()
+        return main_error_404.main_error_404_2(conn)

+ 3 - 0
route/main_other.py

@@ -14,6 +14,8 @@ def main_other_2(conn):
             <br>
             <h2>''' + load_lang('list') + '''</h2>
             <ul>
+                <li><a href="/recent_changes">''' + load_lang('recent_change') + '''</a></li>
+                <li><a href="/recent_discuss">''' + load_lang('recent_discussion') + '''</a></li>
                 <li><a href="/admin_list">''' + load_lang('admin_list') + '''</a></li>
                 <li><a href="/not_close_topic">''' + load_lang('open_discussion_list') + '''</a></li>
                 <li><a href="/title_index">''' + load_lang('all_document_list') + '''</a></li>
@@ -24,6 +26,7 @@ def main_other_2(conn):
                 <li><a href="/admin_log">''' + load_lang('authority_use_list') + '''</a></li>
                 <li><a href="/old_page">''' + load_lang('old_page') + '''</a></li>
                 <li><a href="/image_file_list">''' + load_lang('image_file_list') + '''</a></li>
+                <li><a href="/vote">''' + load_lang('vote_list') + '''</a></li>
             </ul>
             <br>
             <h2>''' + load_lang('other') + '''</h2>

+ 25 - 19
route/main_views.py

@@ -1,28 +1,34 @@
 from .tool.func import *
+from . import main_error_404
 
 def main_views_2(conn, name):
     curs = conn.cursor()
 
-    if re.search(r'\/', name):
-        m = re.search(r'^(.*)\/(.*)$', name)
-        if m:
-            n = m.groups()
-            plus = '/' + n[0]
-            rename = n[1]
-        else:
-            plus = ''
-            rename = name
+    file_name = re.search(r'([^/]+)$', name)
+    if not file_name:
+        return main_error_404.main_error_404_2(conn)
     else:
-        plus = ''
-        rename = name
+        file_name = file_name.group(1)
+        dir_name = './views/' + re.sub(r'\.{2,}', '', re.sub(r'([^/]+)$', '', name))
 
-    mime_type = re.search(r'\.([^\.]+)$', rename).group(1)
-    if mime_type:
-        if mime_type in ['.jpeg', '.jpg', '.gif', '.png', '.webp', '.JPEG', '.JPG', '.GIF', '.PNG', '.WEBP']:
-            mime_type = 'image/' + mime_type
+        mime_type = re.search(r'([^.]+)$', file_name)
+        image_type = [
+            '.jpeg', 
+            '.jpg', 
+            '.gif', 
+            '.png', 
+            '.webp'
+        ]
+        if mime_type:
+            mime_type = mime_type.group(1).lower()
+            if mime_type in image_type:
+                mime_type = 'image/' + mime_type
+            else:
+                mime_type = 'text/' + mime_type
         else:
-            mime_type = 'text/' + mime_type
-    else:
-        mime_type = 'text/plain'
+            mime_type = 'text/plain'
 
-    return flask.send_from_directory('./views' + plus, rename, mimetype = mime_type)
+        return flask.send_from_directory(
+            dir_name, file_name, 
+            mimetype = mime_type
+        )

+ 8 - 4
route/recent_changes.py

@@ -13,7 +13,8 @@ def recent_changes_2(conn, name, tool):
         ban = ''
         select = ''
         sub = ''
-
+        admin_6 = admin_check(6)
+        admin = admin_check()
         div = '''
             <table id="main_table_set">
                 <tbody>
@@ -143,7 +144,7 @@ def recent_changes_2(conn, name, tool):
             date = data[2]
 
             if data[6] == 'O':
-                if admin_check(6) == 1:
+                if admin == 1:
                     style[0] = 'id="toron_color_grey"'
                     style[1] = 'id="toron_color_grey"'
 
@@ -162,7 +163,10 @@ def recent_changes_2(conn, name, tool):
                 title = '<a href="/w/' + url_pas(name) + '?num=' + data[0] + '">r' + data[0] + '</a> '
             else:
                 title = '<a href="/w/' + url_pas(data[1]) + '">' + html.escape(data[1]) + '</a> '
-                title += '<a href="/history/' + url_pas(data[1]) + '">(r' + data[0] + ')</a> '
+                if int(data[0]) < 2:
+                    title += '<a href="/history/' + url_pas(data[1]) + '">(r' + data[0] + ')</a> '
+                else:
+                    title += '<a href="/diff/' + url_pas(data[1]) + '?first=' + str(int(data[0]) - 1) + '&second=' + data[0] + '">(r' + data[0] + ')</a> '
 
             div += '''
                 <tr ''' + style[0] + '''>
@@ -200,7 +204,7 @@ def recent_changes_2(conn, name, tool):
 
                     menu = [['w/' + url_pas(name), load_lang('return')]]
 
-                    if admin_check() == 1:
+                    if admin == 1:
                         menu += [['add_history/' + url_pas(name), load_lang('history_add')]]
                 else:
                     menu = [['history/' + url_pas(name), load_lang('return')]]

+ 20 - 42
route/recent_history_tool.py

@@ -5,56 +5,34 @@ def recent_history_tool_2(conn, name):
 
     num = str(int(number_check(flask.request.args.get('num', '1'))))
 
-    data = '''
-        <h2>''' + load_lang('tool') + '''</h2>
-        <ul>
-            <li>
-                <a href="/raw/''' + url_pas(name) + '?num=' + num + '">' + load_lang('raw') + '''</a>
-            </li>
-    '''
-
-    if (int(num) - 1) > 0:
-        data += '''
-            <li>
-                <a href="/diff/''' + url_pas(name) + '?first=''' + str(int(num) - 1) + '&second=' + num + '">' + load_lang('compare') + '''</a>
-            </li>
-        '''
+    data = '' + \
+        '<h2>' + load_lang('tool') + '</h2>' + \
+        '<ul>' + \
+            '<li><a href="/raw/' + url_pas(name) + '?num=' + num + '">' + load_lang('raw') + '</a></li>' + \
+    ''
 
     if flask.request.args.get('type', '') == 'history':
-        data += '''
-            <li>
-                <a href="/revert/''' + url_pas(name) + '?num=' + num + '">' + load_lang('revert') + '''</a>
-            </li>
-        '''
+        data += '<li><a href="/revert/' + url_pas(name) + '?num=' + num + '">' + load_lang('revert') + '</a></li>'
+        if (int(num) - 1) > 0:
+            data += '<li><a href="/diff/' + url_pas(name) + '?first=' + str(int(num) - 1) + '&second=' + num + '">' + load_lang('compare') + '</a></li>'
     elif (int(num) - 1) > 0:
-        data += '''
-            <li>
-                <a href="/revert/''' + url_pas(name) + '?num=' + str(int(num) - 1) + '">' + load_lang('revert') + '''</a>
-            </li>
-        '''
+        data += '<li><a href="/revert/' + url_pas(name) + '?num=' + str(int(num) - 1) + '">' + load_lang('revert') + '</a></li>'
+
+    if flask.request.args.get('type', '') != 'history':
+        data += '<li><a href="/history/' + url_pas(name) + '">' + load_lang('history') + '</a></li>'
 
     if admin_check(6) == 1:
-        curs.execute(db_change('''
-            select title from history
-            where title = ? and id = ? and hide = 'O'
-        '''), [name, num])
+        curs.execute(db_change('' + \
+            'select title from history ' + \
+            'where title = ? and id = ? and hide = "O"' + \
+        ''), [name, num])
         hide = curs.fetchall()
-        data += '''
-            <li>
-                <a href="/hidden/''' + url_pas(name) + '?num=' + num + '">' + (load_lang('hide_release') if hide else load_lang('hide')) + '''
-            </li>
-        '''
+        data += '<li><a href="/hidden/' + url_pas(name) + '?num=' + num + '">' + (load_lang('hide_release') if hide else load_lang('hide')) + '</li>'
 
     if admin_check() == 1:
-        data += '''
-            <li>
-                <a href="/history_delete/''' + url_pas(name) + '?num=' + num + '">' + load_lang('history_delete') + '''
-            </li>
-        '''
-
-    data += '''
-        </ul>
-    '''
+        data += '<li><a href="/history_delete/' + url_pas(name) + '?num=' + num + '">' + load_lang('history_delete') + '</li>'
+
+    data += '</ul>'
 
     return easy_minify(flask.render_template(skin_check(),
         imp = [name, wiki_set(), custom(), other2(['(r' + num + ')', 0])],

+ 2 - 5
route/server_now_update.py

@@ -11,10 +11,7 @@ def server_now_update_2(conn, r_ver):
 
         curs.execute(db_change('select data from other where name = "update"'))
         up_data = curs.fetchall()
-        if up_data:
-            up_data = up_data[0][0]
-        else:
-            up_data = 'stable'
+        up_data = up_data[0][0] if up_data and up_data[0][0] in ['stable', 'beta', 'dev'] else 'stable'
 
         print('----')
         print('Update')
@@ -53,7 +50,7 @@ def server_now_update_2(conn, r_ver):
                     <li>''' + load_lang('version') + ' : ' + r_ver + '''</li>
                     <li id="ver_send" style="display: none;">''' + load_lang('lastest') + ''' : </li>
                 </ul>
-                <a href="https://github.com/2du/openNAMU">(Master)</a> <a href="https://github.com/2du/openNAMU/tree/stable">(Stable)</a>
+                <a href="https://github.com/2du/openNAMU">(Beta)</a> <a href="https://github.com/2du/openNAMU/tree/stable">(Stable)</a>
                 <hr class=\"main_hr\">
                 <form method="post">
                     <button type="submit">''' + load_lang('update') + '''</button>

+ 20 - 10
route/setting.py

@@ -122,13 +122,13 @@ def setting_2(conn, num, db_set):
                 if d_list[acl_num]:
                     check_box_div[i] = 'checked="checked"'
 
-            branch_div =''
-            if d_list[12] == 'stable':
-                branch_div += '<option value="stable">stable</option>'
-                branch_div += '<option value="master">master</option>'
-            else:
-                branch_div += '<option value="master">master</option>'
-                branch_div += '<option value="stable">stable</option>'
+            branch_div = ''
+            branch_list = ['stable', 'dev', 'beta']
+            for i in branch_list:
+                if d_list[12] == i:
+                    branch_div = '<option value="' + i + '">' + i + '</option>' + branch_div
+                else:
+                    branch_div += '<option value="' + i + '">' + i + '</option>'
 
             if db_set != 'sqlite':
                 sqlite_only = 'style="display:none;"'
@@ -169,7 +169,7 @@ def setting_2(conn, num, db_set):
                         </span>
                         <span>''' + load_lang('wiki_skin') + '''</span>
                         <hr class="main_hr">
-                        <select name="skin">''' + load_skin(d_list[5]) + '''</select>
+                        <select name="skin">''' + load_skin(d_list[5] if d_list[5] != '' else 'marisa') + '''</select>
                         <hr class="main_hr">
                         <input type="checkbox" name="reg" ''' + check_box_div[0] + '''> ''' + load_lang('no_register') + '''
                         <hr class="main_hr">
@@ -227,7 +227,8 @@ def setting_2(conn, num, db_set):
             'error_401',
             'error_404',
             'approval_question',
-            'edit_help'
+            'edit_help',
+            'upload_help'
         ]
         if flask.request.method == 'POST':
             for i in i_list:
@@ -316,6 +317,10 @@ def setting_2(conn, num, db_set):
                         <hr class="main_hr">
                         <textarea rows="3" name="''' + i_list[13] + '''">''' + html.escape(d_list[13]) + '''</textarea>
                         <hr class="main_hr">
+                        <span>''' + load_lang('upload_help') + '''</span>
+                        <hr class="main_hr">
+                        <textarea rows="3" name="''' + i_list[14] + '''">''' + html.escape(d_list[14]) + '''</textarea>
+                        <hr class="main_hr">
                         <button id="save" type="submit">''' + load_lang('save') + '''</button>
                         <hr class="main_hr">
                         <ul>
@@ -573,7 +578,8 @@ def setting_2(conn, num, db_set):
             2 : 'discussion',
             3 : 'upload_acl',
             4 : 'all_view_acl',
-            5 : 'many_upload_acl'
+            5 : 'many_upload_acl',
+            6 : 'vote_acl'
         }
 
         if flask.request.method == 'POST':
@@ -649,6 +655,10 @@ def setting_2(conn, num, db_set):
                         <hr class="main_hr">
                         <select ''' + disable + ''' name="many_upload_acl">''' + acl_div[4] + '''</select>
                         <hr class="main_hr">
+                        <span>''' + load_lang('vote_acl') + '''</span>
+                        <hr class="main_hr">
+                        <select ''' + disable + ''' name="vote_acl">''' + acl_div[5] + '''</select>
+                        <hr class="main_hr">
                         <button id="save" type="submit">''' + load_lang('save') + '''</button>
                     </form>
                 ''',

+ 86 - 70
route/tool/func.py

@@ -13,6 +13,7 @@ for i in range(0, 2):
         import tornado.wsgi
         import urllib.request
         import email.mime.text
+        import requests
         import sqlite3
         import pymysql
         import hashlib
@@ -263,9 +264,32 @@ def update(ver_num, set_data):
                 get_data[2]
             ])
 
-    if ver_num < 3200900:
+    if ver_num < 3202400:
+        curs.execute(db_change("select data from other where name = 'update'"))
+        get_data = curs.fetchall()
+        if get_data and get_data[0][0] == 'master':
+            curs.execute(db_change("update other set data = 'beta' where name = 'update'"), [])
+
+    if ver_num < 3202500:
         curs.execute(db_change('delete from cache_data'))
 
+    if ver_num < 3202600:
+        curs.execute(db_change("select name, regex, sub from filter"))
+        for i in curs.fetchall():
+            curs.execute(db_change("insert into html_filter (html, kind, plus, plus_t) values (?, 'regex_filter', ?, ?)"), [
+                i[0], 
+                i[1],
+                i[2]
+            ])
+
+        curs.execute(db_change("select title, link, icon from inter"))
+        for i in curs.fetchall():
+            curs.execute(db_change("insert into html_filter (html, kind, plus, plus_t) values (?, 'inter_wiki', ?, ?)"), [
+                i[0], 
+                i[1],
+                i[2]
+            ])
+
     conn.commit()
 
     print('Update pass')
@@ -340,7 +364,7 @@ def captcha_get():
                     data += '' + \
                         '<script src="https://www.google.com/recaptcha/api.js" async defer></script>' + \
                         '<div class="g-recaptcha" data-sitekey="' + recaptcha[0][0] + '"></div>' + \
-                        '<hr class=\"main_hr\">' + \
+                        '<hr class="main_hr">' + \
                     ''
                 else:
                     data += '' + \
@@ -453,9 +477,15 @@ def ip_warring():
         curs.execute(db_change('select data from other where name = "no_login_warring"'))
         data = curs.fetchall()
         if data and data[0][0] != '':
-            text_data = '<span>' + data[0][0] + '</span><hr class=\"main_hr\">'
+            text_data = '' + \
+                '<span>' + data[0][0] + '</span>' + \
+                '<hr class="main_hr">' + \
+            ''
         else:
-            text_data = '<span>' + load_lang('no_login_warring') + '</span><hr class=\"main_hr\">'
+            text_data = '' + \
+                '<span>' + load_lang('no_login_warring') + '</span>' + \
+                '<hr class="main_hr">' + \
+            ''
     else:
         text_data = ''
 
@@ -502,27 +532,29 @@ def next_fix(link, num, page, end = 50):
 
 def other2(data):
     global req_list
-    main_css_ver = '50'
+    main_css_ver = '53'
     data += ['' for _ in range(0, 3 - len(data))]
 
     if req_list == '':
         for i_data in os.listdir(os.path.join("views", "main_css", "css")):
-            req_list += '<link rel="stylesheet" href="/views/main_css/css/' + i_data + '?ver=' + main_css_ver + '">'
+            if i_data != 'sub':
+                req_list += '<link rel="stylesheet" href="/views/main_css/css/' + i_data + '?ver=' + main_css_ver + '">'
 
         for i_data in os.listdir(os.path.join("views", "main_css", "js")):
-            req_list += '<script src="/views/main_css/js/' + i_data + '?ver=' + main_css_ver + '"></script>'
+            if i_data != 'sub':
+                req_list += '<script src="/views/main_css/js/' + i_data + '?ver=' + main_css_ver + '"></script>'
 
     data = data[0:2] + ['', '''
         <link   rel="stylesheet"
-                href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/styles/default.min.css">
+                href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.2/build/styles/default.min.css">
         <link   rel="stylesheet"
-                href="https://cdn.jsdelivr.net/npm/katex@0.10.1/dist/katex.min.css"
-                integrity="sha384-dbVIfZGuN1Yq7/1Ocstc1lUEm+AT+/rCkibIcC/OmWo5f0EA48Vf8CytHzGrSwbQ"
+                href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css"
+                integrity="sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X"
                 crossorigin="anonymous">
-        <script src="https://cdn.jsdelivr.net/npm/katex@0.10.1/dist/katex.min.js"
-                integrity="sha384-2BKqo+exmr9su6dir+qCw08N2ZKRucY4PrGQPPWU1A7FtlCGjmEGFqXCv5nyM5Ij"
+        <script src="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js"
+                integrity="sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4"
                 crossorigin="anonymous"></script>
-        <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/highlight.min.js"></script>
+        <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.2/build/highlight.min.js"></script>
         <script>window.addEventListener('DOMContentLoaded', function() { main_css_skin_load(); });</script>
     ''' + req_list] + data[2:]
 
@@ -677,25 +709,14 @@ def ip_pas(raw_ip, type_d = 0):
                 curs.execute(db_change("select data from other where name = 'ip_view'"))
                 data = curs.fetchall()
                 if data and data[0][0] != '':
-                    if re.search(r'\.', raw_ip):
-                        ip = re.sub(r'\.([^.]*)\.([^.]*)$', '.*.*', raw_ip)
-                    else:
-                        ip = re.sub(r':([^:]*):([^:]*)$', ':*:*', raw_ip)
-
-                    if not admin_check(1):
-                        hide = 1
+                    ip = re.sub(r'\.([^.]*)\.([^.]*)$', '.*.*', raw_ip) if re.search(r'\.', raw_ip) else re.sub(r':([^:]*):([^:]*)$', ':*:*', raw_ip)
+                    hide = 1 if not admin_check(1) else 0
                 else:
                     ip = raw_ip
             else:
                 if type_d == 0:
-                    curs.execute(db_change("select title from data where title = ?"), ['user:' + raw_ip])
-                    if curs.fetchall():
-                        ip = '<a href="/w/' + url_pas('user:' + raw_ip) + '">' + raw_ip + '</a>'
-                    else:
-                        ip = '<a id="not_thing" href="/w/' + url_pas('user:' + raw_ip) + '">' + raw_ip + '</a>'
-
-                    if admin_check('all', None, raw_ip) == 1:
-                        ip = '<b>' + ip + '</b>'
+                    ip = '<a href="/w/' + url_pas('user:' + raw_ip) + '">' + raw_ip + '</a>'
+                    ip = '<b>' + ip + '</b>' if admin_check('all', None, raw_ip) == 1 else ip
                 else:
                     ip = raw_ip
 
@@ -703,21 +724,17 @@ def ip_pas(raw_ip, type_d = 0):
                 if ban_check(raw_ip) == 1:
                     ip = '<s>' + ip + '</s>'
 
-                if hide == 0:
-                    ip += ' <a href="/tool/' + url_pas(raw_ip) + '">(' + load_lang('tool') + ')</a>'
+                    if ban_check(raw_ip, 'login') == 1:
+                        ip = '<i>' + ip + '</i>'
+
+                ip = (ip + ' <a href="/tool/' + url_pas(raw_ip) + '">(' + load_lang('tool') + ')</a>') if hide == 0 else ip
         
             end_ip[raw_ip] = ip
 
     return ip if return_ip == 1 else end_ip
 
 def custom():
-    if 'head' in flask.session:
-        if len(re.findall('<', flask.session['head'])) % 2 != 1:
-            user_head = flask.session['head']
-        else:
-            user_head = ''
-    else:
-        user_head = ''
+    user_head = flask.session['head'] if 'head' in flask.session else ''
 
     ip = ip_check()
     if ip_or_user(ip) == 0:
@@ -738,20 +755,14 @@ def custom():
             for i in user_acl:
                 user_acl_list += [i[0]]
 
-            if user_acl != []:
-                user_acl_list = user_acl_list
-            else:
-                user_acl_list = '0'
+            user_acl_list = user_acl_list if user_acl != [] else '0'
         else:
             user_admin = '0'
             user_acl_list = '0'
 
         curs.execute(db_change("select count(*) from alarm where name = ?"), [ip])
         count = curs.fetchall()
-        if count:
-            user_notice = str(count[0][0])
-        else:
-            user_notice = '0'
+        user_notice = str(count[0][0]) if count else '0'
     else:
         user_icon = 0
         user_name = load_lang('user')
@@ -807,15 +818,17 @@ def load_skin(data = '', set_n = 0, default = 0):
         data = [[data]]
 
     for skin_data in skin_list_get:
+        see_data = skin_data if skin_data != 'default' else load_lang('default')
+
         if not skin_data in system_file:
             if data[0][0] == skin_data:
                 if set_n == 0:
-                    skin_return_data = '<option value="' + skin_data + '">' + skin_data + '</option>' + skin_return_data
+                    skin_return_data = '<option value="' + skin_data + '">' + see_data + '</option>' + skin_return_data
                 else:
                     skin_return_data = [skin_data] + skin_return_data
             else:
                 if set_n == 0:
-                    skin_return_data += '<option value="' + skin_data + '">' + skin_data + '</option>'
+                    skin_return_data += '<option value="' + skin_data + '">' + see_data + '</option>'
                 else:
                     skin_return_data += [skin_data]                    
 
@@ -878,7 +891,7 @@ def acl_check(name = 'test', tool = '', topic_num = '1'):
             name = name[0][0] if name else 'test'
         
         end = 3
-    elif tool == 'render' or tool == '':
+    elif tool == 'render' or tool == '' or tool == 'vote':
         if tool == '' and acl_check(name, 'render') == 1:
             return 1
 
@@ -911,6 +924,11 @@ def acl_check(name = 'test', tool = '', topic_num = '1'):
             curs.execute(db_change("select data from other where name = 'many_upload_acl'"))
 
             num = 5
+        elif tool == 'vote':
+            if i == 0:
+                curs.execute(db_change('select acl from vote where id = ? and user = ""'), [topic_num])
+            else:
+                curs.execute(db_change('select data from other where name = "vote_acl"'))
         else:
             # tool == 'render'
             if i == 0:
@@ -921,7 +939,9 @@ def acl_check(name = 'test', tool = '', topic_num = '1'):
             num = 5
 
         acl_data = curs.fetchall()
-        if (not acl_data and i == (end - 1)) and get_ban == 1 and tool != 'render':
+        if  (i == (end - 1) and (not acl_data or acl_data[0][0] == '' or acl_data[0][0] == 'normal')) and \
+            get_ban == 1 and \
+            tool != 'render':
             return 1
         elif acl_data and acl_data[0][0] != 'normal' and acl_data[0][0] != '':
             if acl_data[0][0] != 'ban' and get_ban == 1 and tool != 'render':
@@ -998,8 +1018,7 @@ def acl_check(name = 'test', tool = '', topic_num = '1'):
     return 1
 
 def ban_check(ip = None, tool = None):
-    if not ip:
-        ip = ip_check()
+    ip = ip_check() if not ip else ip
 
     if admin_check(None, None, ip) == 1:
         return 0
@@ -1037,14 +1056,9 @@ def ban_check(ip = None, tool = None):
 
 def ban_insert(name, end, why, login, blocker, type_d = None):
     now_time = get_time()
-
-    if type_d:
-        band = type_d
-    else:
-        band = ''
+    band = type_d if type_d else ''
 
     curs.execute(db_change("update rb set ongoing = '' where end < ? and end != '' and ongoing = '1'"), [now_time])
-
     curs.execute(db_change("" + \
         "select block from rb where ((end > ? and end != '') or end = '') and block = ? and band = ? and ongoing = '1'" + \
     ""), [now_time, name, band])
@@ -1059,27 +1073,24 @@ def ban_insert(name, end, why, login, blocker, type_d = None):
         ])
         curs.execute(db_change("update rb set ongoing = '' where block = ? and band = ? and ongoing = '1'"), [name, band])
     else:
-        if login != '':
-            login = 'O'
-        else:
-            login = ''
+        login = 'O' if login != '' else ''
 
         if end != '0':
             end = int(number_check(end))
-
             time = datetime.datetime.now()
             plus = datetime.timedelta(seconds = end)
             r_time = (time + plus).strftime("%Y-%m-%d %H:%M:%S")
         else:
             r_time = ''
 
-        curs.execute(db_change("insert into rb (block, end, today, blocker, why, band, ongoing) values (?, ?, ?, ?, ?, ?, '1')"), [
+        curs.execute(db_change("insert into rb (block, end, today, blocker, why, band, ongoing, login) values (?, ?, ?, ?, ?, ?, '1', ?)"), [
             name, 
             r_time, 
             now_time, 
             blocker, 
             why, 
-            band
+            band,
+            login
         ])
 
     conn.commit()
@@ -1093,16 +1104,21 @@ def rd_plus(topic_num, date, name = None, sub = None):
 
     conn.commit()
 
-def history_plus(title, data, date, ip, send, leng, t_check = '', d_type = ''):
-    curs.execute(db_change("select id from history where title = ? and type = '' order by id + 0 desc limit 1"), [title])
-    id_data = curs.fetchall()
-    id_data = str(int(id_data[0][0]) + 1) if id_data else '1'
+def history_plus(title, data, date, ip, send, leng, t_check = '', d_type = '', mode = ''):
+    if mode == 'add':
+        curs.execute(db_change("select id from history where title = ? and type = '' order by id + 0 asc limit 1"), [title])
+        id_data = curs.fetchall()
+        id_data = str(int(id_data[0][0]) - 1) if id_data else '0'
+    else:
+        curs.execute(db_change("select id from history where title = ? and type = '' order by id + 0 desc limit 1"), [title])
+        id_data = curs.fetchall()
+        id_data = str(int(id_data[0][0]) + 1) if id_data else '1'
 
     send = re.sub(r'\(|\)|<|>', '', send)
     send = send[:128] if len(send) > 128 else send
     send = send + ' (' + t_check + ')' if t_check != '' else send
 
-    if not re.search('^user:', title):
+    if not re.search('^user:', title) and mode != 'add':
         curs.execute(db_change("select count(*) from rc where type = 'normal'"))
         if curs.fetchall()[0][0] > 49:
             curs.execute(db_change("select id, title from rc where type = 'normal' order by date asc limit 1"))
@@ -1149,7 +1165,7 @@ def number_check(data):
 
 def edit_filter_do(data):
     if admin_check(1) != 1:
-        curs.execute(db_change("select regex, sub from filter where regex != ''"))
+        curs.execute(db_change("select plus, plus_t from html_filter where kind = 'regex_filter' and plus != ''"))
         for data_list in curs.fetchall():
             match = re.compile(data_list[0], re.I)
             if match.search(data):

+ 0 - 1
route/tool/mark.py

@@ -10,7 +10,6 @@ import sqlite3
 import asyncio
 import threading
 import urllib.parse
-import multiprocessing
 
 if os.path.exists('route/tool/set_mark/custom.py'):
     from .set_mark.custom import custom_mark

+ 84 - 21
route/tool/set_mark/markdown.py

@@ -6,22 +6,51 @@ import re
 
 class head_render:
     def __init__(self):
-        pass
+        self.head_level = [0, 0, 0, 0, 0, 0]
+        self.toc_data = '' + \
+            '<div id="toc">' + \
+                '<span id="toc_title">TOC</span>' + \
+                '<br>' + \
+                '<br>' + \
+        ''
+        self.toc_num = 0
 
     def __call__(self, match):
-        head_len = str(len(match[1]))
+        head_len_num = len(match[1])
+        head_len = str(head_len_num)
+        head_len_num -= 1
         head_data = match[2]
+        self.head_level[head_len_num] += 1
+        for i in range(head_len_num + 1, 6):
+            self.head_level[i] = 0
+            
+        self.toc_num += 1
+        toc_num_str = str(self.toc_num)
+        head_level_str_2 = '.'.join([str(i) for i in self.head_level if i != 0])
+        head_level_str = head_level_str_2 + '.'
 
-        return '<h' + head_len + '>' + head_data + '</h' + head_len + '>'
+        self.toc_data += '<a href="#s-' + head_level_str_2 + '">' + head_level_str + '</a> ' + head_data + '<br>'
+        return '<h' + head_len + ' id="s-' + head_level_str_2 + '"><a href="#toc">' + head_level_str + '</a> ' + head_data + '</h' + head_len + '>'
+
+    def get_toc(self):
+        return self.toc_data + '</div>'
 
 class link_render:
-    def __init__(self):
-        pass
+    def __init__(self, plus_data, include_name):
+        self.str_e_link_id = 0
+        self.plus_data = ''
+        self.include_name = include_name
 
     def __call__(self, match):
-        if match[1] == '!':
+        str_e_link_id = str(self.str_e_link_id)
+        self.str_e_link_id += 1
+
+        if match[1] == '!':    
+            file_name = ''
             if re.search(r'^http(s)?:\/\/', match[3], flags = re.I):
-                return '<img alt="' + match[2] + '" src="' + match[3] + '">'
+                file_src = match[3]
+                file_alt = match[3]
+                exist = '1'
             else:
                 file_name = re.search(r'^([^.]+)\.([^.]+)$', match[3])
                 if file_name:
@@ -33,23 +62,53 @@ class link_render:
 
                 file_src = '/image/' + tool.sha224_replace(file_name) + '.' + file_end
                 file_alt = 'file:' + file_name + '.' + file_end
+                exist = None
 
-                return '' + \
-                    '<img class="' + include_num + 'file_finder_1" alt="' + match[2] + '" src="' + file_src + '">' + \
-                    '<a class="' + include_num + 'file_finder_2" id="not_thing" href="/upload?name=' + tool.url_pas(file_name) + '">' + file_alt + '</a>' + \
-                ''
+            return '' + \
+                '<span  class="' + self.include_name + 'file_finder" ' + \
+                        'under_alt="' + file_alt + '" ' + \
+                        'under_src="' + file_src + '" ' + \
+                        'under_style="" ' + \
+                        'under_href="' + ("out_link" if exist else '/upload?name=' + tool.url_pas(file_name)) + '">' + \
+                '</span>' + \
+            ''
         else:
             if re.search(r'^http(s)?:\/\/', match[3], flags = re.I):
-                return '<a id="out_link" href="' + match[3] + '">' + match[2] + '</a>'
+                self.plus_data += '' + \
+                    'document.getElementsByName("' + self.include_name + 'set_link_' + str_e_link_id + '")[0].href = ' + \
+                        '"' + match[3] + '";' + \
+                    '\n' + \
+                ''
+
+                return '<a  id="out_link" ' + \
+                            'href="" ' + \
+                            'name="' + self.include_name + 'set_link_' + str_e_link_id + '">' + match[2] + '</a>'
             else:
-                return '<a class="' + include_num + 'link_finder" href="/w/' + match[3] + '">' + match[2] + '</a>'
+                self.plus_data += '' + \
+                    'document.getElementsByName("' + self.include_name + 'set_link_' + str_e_link_id + '")[0].href = ' + \
+                        '"/w/' + tool.url_pas(match[3]) + '";' + \
+                    '\n' + \
+                ''
+                self.plus_data += '' + \
+                    'document.getElementsByName("' + self.include_name + 'set_link_' + str_e_link_id + '")[0].title = ' + \
+                        '"' + match[3] + '";' + \
+                    '\n' + \
+                ''
+
+                return '<a  class="' + self.include_name + 'link_finder" ' + \
+                            'title="" ' + \
+                            'href="" ' + \
+                            'name="' + self.include_name + 'set_link_' + str_e_link_id + '">' + match[2] + '</a>'
 
-def markdown(conn, data, title, include_num):
+    def get_plus_data(self):
+        return self.plus_data
+
+def markdown(conn, data, title, include_name):
     backlink = []
-    include_num = include_num + '_' if include_num else ''
+    include_name = include_name + '_' if include_name else ''
     plus_data = '' + \
-        'get_link_state("' + include_num + '");\n' + \
-        'get_file_state("' + include_num + '");\n' + \
+        'get_link_state("' + include_name + '");\n' + \
+        'get_file_state("' + include_name + '");\n' + \
     ''
 
     data = html.escape(data)
@@ -59,15 +118,19 @@ def markdown(conn, data, title, include_num):
     head_r = r'\n(#{1,6}) ?([^\n]+)'
     head_do = head_render()
     data = re.sub(head_r, head_do, data)
+    data = head_do.get_toc() + data
 
     link_r = r'(!)?\[((?:(?!\]\().)+)\]\(([^\]]+)\)'
-    link_do = link_render()
+    link_do = link_render(plus_data, include_name)
     data = re.sub(link_r, link_do, data)
+    plus_data = link_do.get_plus_data() + plus_data
 
-    data = re.sub(r'\*\*((?:(?!\*\*).)+)\*\*', '<b>\1</b>', data)
-    data = re.sub(r'__((?:(?!__).)+)__', '<i>\1</i>', data)
+    data = re.sub(r'\*\*(?P<A>(?:(?!\*\*).)+)\*\*', '<b>\g<A></b>', data)
+    data = re.sub(r'__(?P<A>(?:(?!__).)+)__', '<i>\g<A></i>', data)
 
     data = re.sub('^\n', '', data)
-    data = re.sub('\n', '<br>', data)
+    data = data.replace('\n', '<br>')
+
+    data = re.sub(r'(?P<A><\/h[0-6]>)<br>', '\g<A>', data)
     
     return [data, plus_data, backlink]

+ 183 - 227
route/tool/set_mark/namumark.py

@@ -37,13 +37,13 @@ def link_fix(main_link, no_change = 0):
         other_link = ''
 
     main_link = main_link.replace("<link_comma>", "&#x27;")
-    main_link = re.sub(r'\\#', '%23', main_link)
+    main_link = main_link.replace('\\#', '%23')
 
-    find_data = re.findall(r'<span id="(nowiki_[0-9]+)">', main_link)
+    find_data = re.findall(r'<span id="((?:include_(?:[0-9]+)_)?(?:nowiki_[0-9]+))">', main_link)
     for i in find_data:
         main_link = main_link.replace('<span id="' + i + '"></span>', end_data[i])
 
-    find_data = re.findall(r'<span id="(nowiki_[0-9]+)">', other_link)
+    find_data = re.findall(r'<span id="((?:include_(?:[0-9]+)_)?(?:nowiki_[0-9]+))">', other_link)
     for i in find_data:
         other_link = other_link.replace('<span id="' + i + '"></span>', end_data[i])
 
@@ -353,12 +353,13 @@ def middle_parser(data):
 
                         data = re.sub(
                             r'{{{#!wiki(?: style=(?:&quot;|&#x27;)((?:(?!&quot;|&#x27;).)*)(?:&quot;|&#x27;))?(?: *)\n?',
-                            '<div id="wiki_div" style="' + str(middle_data_2[0] if middle_data_2[0] else '') + '">',
+                            '<div_1 style="' + str(middle_data_2[0] if middle_data_2[0] else '') + '">',
                             data,
                             1
                         )
                     elif re.search(r'^#!syntax', middle_data[0]):
-                        middle_data_2 = re.search(r'{{{#!syntax ((?:(?!\n|{{{).)+)\n?', data)
+                        syntax_re = re.compile(r'{{{#!syntax ?((?:(?!\n|{{{|}}}).)*)\n?')
+                        middle_data_2 = syntax_re.search(data)
                         if middle_data_2:
                             middle_data_2 = middle_data_2.groups()
                         else:
@@ -371,14 +372,13 @@ def middle_parser(data):
 
                         middle_list += ['pre']
 
-                        data = re.sub(
-                            r'{{{#!syntax ?((?:(?!\n|{{{).)*)\n?',
+                        data = syntax_re.sub(
                             '<pre id="syntax"><code class="' + middle_data_2[0] + '">',
                             data,
                             1
                         )
                     elif re.search(r'^#!folding', middle_data[0]):
-                        middle_list += ['2div']
+                        middle_list += ['div_dd']
 
                         folding_data = re.search(r'{{{#!folding ?((?:(?!\n).)*)\n?', data)
                         if folding_data:
@@ -406,7 +406,7 @@ def middle_parser(data):
                                     '</b>' + \
                                 '</div_2>' + \
                                 '<div id="' + include_name + 'folding_' + str(folding_num) + '" style="display: none;">' + \
-                                    '<div id="wiki_div" style="">',
+                                    '<div_1 style="">\n',
                             data,
                             1
                         )
@@ -443,7 +443,7 @@ def middle_parser(data):
                     if middle_num > 0:
                         middle_num -= 1
 
-                    if middle_list[middle_num] == '2div':
+                    if middle_list[middle_num] == 'div_dd':
                         data = middle_re.sub('</div_1></div_2></div_2>', data, 1)
                     elif middle_list[middle_num] == 'pre':
                         data = middle_re.sub('</code></pre>', data, 1)
@@ -485,10 +485,9 @@ def middle_parser(data):
             nowiki_num += 1
             end_data[include_name + 'nowiki_' + str(nowiki_num)] = nowiki_data[0]
             plus_data += '' + \
-                'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) { ' + \
-                    'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(nowiki_data[0]) + '"; ' + \
-                '}' + \
-                '\n' + \
+                'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) {\n' + \
+                    'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(nowiki_data[0]) + '";\n' + \
+                '}\n' + \
             ''
 
             data = re.sub(
@@ -511,10 +510,9 @@ def middle_parser(data):
             nowiki_num += 1
             end_data[include_name + 'nowiki_' + str(nowiki_num)] = syntax_data[1]
             plus_data += '' + \
-                'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) { ' + \
-                    'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(syntax_data[1]) + '"; ' + \
-                '}' + \
-                '\n' + \
+                'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) {\n' + \
+                    'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(syntax_data[1]) + '";\n' + \
+                '}\n' + \
             ''
 
             data = re.sub(
@@ -538,18 +536,23 @@ def namumark(conn, data, title, include_num):
 
     nowiki_num = 0
     data = '\n' + data + '\n'
-    include_name = include_num + '_' if include_num else ''
+    include_name = (include_num + '_') if include_num else ''
+    now_time = tool.get_time().split()[0]
     plus_data = ''
 
     backlink = []
     end_data = {}
 
-    data = re.sub(r'@([^=@]+)=(?P<in>[^=@]+)@', '\g<in>', data)
+    data = re.sub(r'@((?:(?!(?:=|{{{|}}}|\[\[|\]\]|@)).)+)=(?P<in>(?:(?!(?:=|{{{|}}}|\[\[|\]\]|@)).)+)@', '\g<in>', data)
     data = re.sub(r'<math>(?P<in>(?:(?!<\/math>).)+)<\/math>', '[math(\g<in>)]', data)
 
     data = html.escape(data)
     data = data.replace('\r\n', '\n')
 
+    # 테이블 앞에 공백 있는 경우 처리 필요
+    # 테이블 col 옵션 적용시 셀 병합 고려 필요
+    data = re.sub(r'\n +\|\|', '\n||', data)
+
     math_re = re.compile(r'\[math\(((?:(?!\)\]).)+)\)\]', re.I)
     while 1:
         math = math_re.search(data)
@@ -581,13 +584,10 @@ def namumark(conn, data, title, include_num):
             data = math_re.sub('<span id="math_' + str(first) + '"></span>', data, 1)
 
             plus_data += '' + \
-                'try {' + \
-                    'katex.render(' + \
-                        '"' + nowiki_js(html.unescape(math)) + '",' + \
-                        'document.getElementById(\"' + include_name + 'math_' + str(first) + '\")' + \
-                    ');' + \
-                '} catch {' + \
-                    'document.getElementById(\"' + include_name + 'math_' + str(first) + '\").innerHTML = "<span style=\'color: red;\'>' + nowiki_js(math) + '</span>";' + \
+                'try {\n' + \
+                    'katex.render("' + nowiki_js(html.unescape(math)) + '", document.getElementById(\"' + include_name + 'math_' + str(first) + '\"));\n' + \
+                '} catch {\n' + \
+                    'document.getElementById(\"' + include_name + 'math_' + str(first) + '\").innerHTML = "<span style=\'color: red;\'>' + nowiki_js(math) + '</span>";\n' + \
                 '}\n' + \
             ''
         else:
@@ -602,10 +602,9 @@ def namumark(conn, data, title, include_num):
             nowiki_num += 1
             end_data[include_name + 'nowiki_' + str(nowiki_num)] = one_nowiki[0]
             plus_data += '' + \
-                'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) { ' + \
-                    'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(one_nowiki[0]) + '"; ' + \
-                '}' + \
-                '\n' + \
+                'if(document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '")) {\n' + \
+                    'document.getElementById("' + include_name + 'nowiki_' + str(nowiki_num) + '").innerHTML = "' + nowiki_js(one_nowiki[0]) + '";\n' + \
+                '}\n' + \
             ''
 
             data = re.sub(r'(?:\\)(.)', '<span id="' + include_name + 'nowiki_' + str(nowiki_num) + '"></span>', data, 1)
@@ -613,80 +612,78 @@ def namumark(conn, data, title, include_num):
             break
 
     include_re = re.compile(r'\[include\(((?:(?!\)\]).)+)\)\]', re.I)
-    i = 0
-    while 1:
-        i += 1
-
-        include = include_re.search(data)
-        if include:
-            include = include.group(1)
-
-            include_data = re.search(r'^((?:(?!,).)+)', include)
-            if include_data:
-                include_data = include_data.group(1)
-            else:
-                include_data = 'Test'
-
-            include_link = include_data
-            backlink += [[title, include_link, 'include']]
+    if include_name == '':
+        i = 0
+        while 1:
+            i += 1
+
+            include = include_re.search(data)
+            if include:
+                include = include.group(1)
+
+                include_data = re.search(r'^((?:(?!,).)+)', include)
+                if include_data:
+                    include_data = include_data.group(1)
+                else:
+                    include_data = 'Test'
 
-            data = include_re.sub('' + \
-                '<a id="' + include_name + 'include_link" class="include_' + str(i) + '" href="/w/' + tool.url_pas(include_link) + '">(' + include_link + ')</a>' + \
-                '<div id="' + include_name + 'include_' + str(i) + '"></div>' + \
-            '', data, 1)
+                include_link = include_data
+                backlink += [[title, include_link, 'include']]
 
-            include_plus_data = []
-            while 1:
-                include_plus = re.search(r', ?((?:(?!=).)+)=((?:(?!,).)+)', include)
-                if include_plus:
-                    include_plus = include_plus.groups()
+                data = include_re.sub('' + \
+                    '<a id="' + include_name + 'include_link" class="include_' + str(i) + '" href="/w/' + tool.url_pas(include_link) + '">(' + include_link + ')</a>' + \
+                    '<div id="' + include_name + 'include_' + str(i) + '"></div>' + \
+                '', data, 1)
 
-                    include_data_set = include_plus[1]
-                    find_data = re.findall(r'<span id="(nowiki_[0-9]+)">', include_data_set)
-                    for j in find_data:
-                        include_data_set = include_data_set.replace('<span id="' + j + '"></span>', end_data[j])
+                include_plus_data = []
+                while 1:
+                    include_plus = re.search(r', ?((?:(?!=).)+)=((?:(?!,).)+)', include)
+                    if include_plus:
+                        include_plus = include_plus.groups()
 
-                    include_plus_data += [[include_plus[0], include_data_set]]
+                        include_data_set = include_plus[1]
+                        find_data = re.findall(r'<span id="((?:include_(?:[0-9]+)_)?(?:nowiki_[0-9]+))">', include_data_set)
+                        for j in find_data:
+                            include_data_set = include_data_set.replace('<span id="' + j + '"></span>', end_data[j])
 
-                    include = re.sub(r', ?((?:(?!=).)+)=((?:(?!,).)+)', '', include, 1)
-                else:
-                    break
+                        include_plus_data += [[include_plus[0], include_data_set]]
 
-            plus_data += 'load_include("' + include_link + '", "' + include_name + 'include_' + str(i) + '", ' + str(include_plus_data) + ');\n'
-        else:
-            break
+                        include = re.sub(r', ?((?:(?!=).)+)=((?:(?!,).)+)', '', include, 1)
+                    else:
+                        break
 
-    data = re.sub(r'\r\n', '\n', data)
-    data = re.sub(r'&amp;', '&', data)
+                plus_data += 'load_include("' + include_link + '", "' + include_name + 'include_' + str(i) + '", ' + str(include_plus_data) + ');\n'
+            else:
+                break
+    else:
+        data = include_re.sub('', data)
 
-    data = re.sub(r'\n##(((?!\n).)+)', '', data)
-    data = re.sub(r'<div id="wiki_div" style="">\n', '<div id="wiki_div" style="">', data)
+    data = data.replace('&amp;', '&')
+    data = re.sub(r'\n##[^\n]+', '', data)
 
+    div_1_re = re.compile(r'<div_1 ([^>]+)>((?:(?!<div_1|<\/div_1>).|\n*)+)<\/div_1>')
     while 1:
-        wiki_table_data = re.search(r'<div id="wiki_div" ((?:(?!>).)+)>((?:(?!<div id="wiki_div"|<\/div_1>).\n*)+)<\/div_1>', data)
+        wiki_table_data = div_1_re.search(data)
         if wiki_table_data:
             wiki_table_data = wiki_table_data.groups()
             if re.search(r'\|\|', wiki_table_data[1]):
-                end_parser = re.sub(r'\n$', '', re.sub(r'^\n', '', table_start('\n' + wiki_table_data[1] + '\n')))
+                end_parser = table_start('\n' + wiki_table_data[1] + '\n')
+                end_parser = re.sub(r'^\n', '', end_parser)
+                end_parser = re.sub(r'\n$', '', end_parser)
             else:
                 end_parser = wiki_table_data[1]
 
-            data = re.sub(
-                '<div id="wiki_div" ((?:(?!>).)+)>((?:(?!<div id="wiki_div"|<\/div_1>).\n*)+)<\/div_1>',
-                '<div ' + wiki_table_data[0] + '>' + end_parser + '</div_2>', 
-                data, 
-                1
-            )
+            data = div_1_re.sub('<div id="wiki_div" ' + wiki_table_data[0] + '>' + end_parser + '</div_2>', data, 1)
         else:
             break
 
-    data = re.sub(r'<\/div_2>', '</div>', data)
-    data = re.sub(r'<\/td>', '</td_1>', data)
+    data = data.replace('</div_2>', '</div>')
+    data = data.replace('</td>', '</td_1>')
 
     data += '\n'
     data = data.replace('\\', '&#92;')
 
-    redirect_re = re.compile(r'\n#(?:redirect|넘겨주기) ((?:(?!\n).)+)\n', re.I)
+    redirect_re = re.compile(r'\n#(?:redirect|넘겨주기) ([^\n]+)', re.I)
     redirect = redirect_re.search(data)
     if redirect:
         redirect = redirect.group(1)
@@ -696,104 +693,81 @@ def namumark(conn, data, title, include_num):
         other_link = return_link[1]
 
         backlink += [[title, main_link, 'redirect']]
-
-        data = redirect_re.sub(
-            '\n' + \
-                '<ul>' + \
-                    '<li>' + \
-                        '<a id="go_redirect_link" href="/w/' + tool.url_pas(main_link) + other_link + '">' + main_link + other_link + '</a>' + \
-                    '</li>' + \
-                '</ul>' + \
-            '\n',
-            data,
-            1
-        )
+        
+        plus_data += '' + \
+            'var get_link = window.location.search.match(/(?:\?|&)from=([^&]+)/);\n' + \
+            'var get_link_2 = window.location.pathname.match(/^\/w\//);' + \
+            'if(!get_link && get_link_2) {\n' + \
+                'window.location.href = "/w/' + tool.url_pas(main_link) + '?from=' + tool.url_pas(title) + other_link + '";\n' + \
+            '}\n' + \
+        ''
+        data = redirect_re.sub('\nredirect to ' + html.escape(main_link) + other_link, data, 1)
 
     no_toc_re = re.compile(r'\[(?:목차|toc)\((?:no)\)\]\n', re.I)
     toc_re = re.compile(r'\[(?:목차|toc)\]', re.I)
     if not no_toc_re.search(data):
         if not toc_re.search(data):
-            data = re.sub(r'\n(?P<in>={1,6}) ?(?P<out>(?:(?!=).)+) ?={1,6}\n', '\n[toc]\n\g<in> \g<out> \g<in>\n', data, 1)
+            data = re.sub(r'(?P<in>\n(={1,6})(#)? ?((?:(?!(?: #=| =)).)+) ?#?(?:=+)\n)', '\n[toc]\g<in>', data, 1)
     else:
         data = no_toc_re.sub('', data)
 
     data = '<div class="all_in_data" id="in_data_0">' + data
 
-    toc_full = 0
-    toc_top_stack = 6
     toc_stack = [0, 0, 0, 0, 0, 0]
-    edit_number = 0
-    toc_data = '<div id="toc"><span id="toc_title">TOC</span>\n\n'
+    toc_num = 0
+    toc_data = '' + \
+        '<div id="toc">' + \
+            '<span id="toc_title">TOC</span>' + \
+            '<br>' + \
+            '<br>' + \
+    ''
+    edit_num = 0
+    toc_head_re = re.compile(r'\n(={1,6})(#)? ?((?:(?!(?: #=| =)).)+) ?#?(?:=+)\n')
     while 1:
-        toc = re.search(r'\n(={1,6}) ?((?:(?!\n).)+) ?(?:={1,6})\n', data)
+        toc = toc_head_re.search(data)
         if toc:
             toc = toc.groups()
+            edit_num += 1
 
-            toc_number = len(toc[0])
-            edit_number += 1
-
-            if toc_full > toc_number:
-                for i in range(toc_number, 6):
-                    toc_stack[i] = 0
+            toc_len_num = len(toc[0])
+            toc_len_str = str(toc_len_num)
+            toc_len_num -= 1
+            toc_stack[toc_len_num] += 1
+            for i in range(toc_len_num + 1, 6):
+                toc_stack[i] = 0
 
-            if toc_top_stack > toc_number:
-                toc_top_stack = toc_number
+            edit_num_str = str(edit_num)
+            toc_level_str = '.'.join([str(i) for i in toc_stack if i != 0])
+            toc_fol = '+' if toc[1] else '-'
 
-            toc_full = toc_number
-            toc_stack[toc_number - 1] += 1
-            toc_number = str(toc_number)
-            all_stack = ''
-
-            for i in range(0, 6):
-                all_stack += str(toc_stack[i]) + '.'
-
-            while 1:
-                if re.search(r'[^0-9]0\.', all_stack):
-                    all_stack = re.sub(r'[^0-9]0\.', '.', all_stack)
-                else:
-                    break
-
-            all_stack = re.sub(r'^0\.', '', all_stack)
-            all_stack = re.sub(r'\.$', '', all_stack)
-
-            new_toc_data = re.sub(r'=*$', '', toc[1])
-            new_toc_data = re.sub(r' +$', '', new_toc_data)
-            if re.search(r'^# ?(?P<in>[^#]+) ?#$', new_toc_data):
-                fol_head = '+'
-
-                new_toc_data = re.sub(r'^# ?(?P<in>[^#]+) ?#$', '\g<in>', new_toc_data)
-            else:
-                fol_head = '-'
-
-            data = re.sub(
-                '\n(={1,6}) ?((?:(?!\n).)+) ?\n',
+            data = toc_head_re.sub(
                 '\n' + \
                 '</div>'
-                '<h' + toc_number + ' id="s-' + all_stack + '">' + \
-                    '<a href="#toc">' + all_stack + '.</a> ' + new_toc_data + ' ' + \
+                '<h' + toc_len_str + ' id="s-' + toc_level_str + '">' + \
+                    '<a href="#toc">' + toc_level_str + '.</a> ' + toc[2] + ' ' + \
                     '<span style="font-size: 12px">' + \
-                        '<a href="/edit/' + tool.url_pas(title) + '?section=' + str(edit_number) + '">(Edit)</a>' + \
+                        '<a href="/edit/' + tool.url_pas(title) + '?section=' + edit_num_str + '">(Edit)</a>' + \
                         ' ' + \
-                        '<a href="javascript:void(0);" onclick="do_open_folding(\'in_data_' + all_stack + '\', this);">' + \
-                            '(' + fol_head + ')' + \
+                        '<a href="javascript:void(0);" onclick="do_open_folding(\'in_data_' + edit_num_str + '\', this);">' + \
+                            '(' + toc_fol + ')' + \
                         '</a>' + \
                     '</span>' + \
-                '</h' + toc_number + '>' + \
-                '<div class="all_in_data"' + (' style="display: none;"' if fol_head == '+' else '') + ' id="in_data_' + all_stack + '">' + \
+                '</h' + toc_len_str + '>' + \
+                '<div class="all_in_data"' + (' style="display: none;"' if toc_fol == '+' else '') + ' id="in_data_' + edit_num_str + '">' + \
                     '\n',
                 data,
                 1
             )
 
-            toc_main_data = new_toc_data
+            toc_main_data = toc[2]
             toc_main_data = re.sub(r'\[\*((?:(?! |\]).)*)(?: ((?:(?!(\[\*(?:(?:(?!\]).)+)\]|\])).)+))?\]', '', toc_main_data)
             toc_main_data = re.sub(r'<span id="math_[0-9]"><\/span>', '(Math)', toc_main_data)
 
             toc_data += '' + \
-                '<span style="margin-left: ' + str((toc_full - toc_top_stack) * 10) + 'px;">' + \
-                    '<a href="#s-' + all_stack + '">' + all_stack + '.</a> ' + toc_main_data + \
+                '<span style="margin-left:' + str(len(re.findall(r'\.', toc_level_str)) * 10) + 'px;">' + \
+                    '<a href="#s-' + toc_level_str + '">' + toc_level_str + '.</a> ' + toc_main_data + \
                 '</span>' + \
-                '\n' + \
+                '<br>' + \
             ''
         else:
             break
@@ -801,10 +775,6 @@ def namumark(conn, data, title, include_num):
     toc_data += '</div>'
     data = toc_re.sub(toc_data, data)
     
-    now_time = tool.get_time()
-    time_data = re.search(r'^([0-9]{4}-[0-9]{2}-[0-9]{2})', now_time)
-    time = time_data.group(1)
-    
     macro_re = re.compile(r'\[([^[(]+)\(((?:(?!\[|\)]).)+)\)\]')
     macro_data = macro_re.findall(data)
     for i in macro_data:
@@ -889,7 +859,7 @@ def namumark(conn, data, title, include_num):
             data = macro_re.sub(ruby_data, data, 1)
         elif macro_name == 'age' or macro_name == 'dday':
             try:
-                old = datetime.datetime.strptime(time, '%Y-%m-%d')
+                old = datetime.datetime.strptime(now_time, '%Y-%m-%d')
                 will = datetime.datetime.strptime(i[1], '%Y-%m-%d')
 
                 e_data = old - will
@@ -897,12 +867,7 @@ def namumark(conn, data, title, include_num):
                 if macro_name == 'age':
                     data = macro_re.sub(str(int(e_data.days / 365)), data, 1)
                 else:
-                    if re.search(r'^-', str(e_data.days)):
-                        e_day = str(e_data.days)
-                    else:
-                        e_day = '+' + str(e_data.days)
-
-                    data = macro_re.sub(e_day, data, 1)
+                    data = macro_re.sub((str(e_data.days) if re.search(r'^-', str(e_data.days)) else ('+' + str(e_data.days))), data, 1)
             except:
                 data = macro_re.sub('age-dday-error', data, 1)
         else:
@@ -912,48 +877,40 @@ def namumark(conn, data, title, include_num):
     data = data.replace('<macro_middle>', '(')
     data = data.replace('<macro_end>', ')]')
 
+    blockquote_re = re.compile(r'(\n(?:&gt; ?(?:[^\n]*)\n)+)')
     while 1:
-        block = re.search(r'(\n(?:&gt; ?(?:(?:(?!\n).)+)?\n)+)', data)
-        if block:
-            block = block.group(1)
-
-            block = re.sub(r'^\n&gt; ?', '', block)
-            block = re.sub(r'\n&gt; ?', '\n', block)
-            block = re.sub(r'\n$', '', block)
+        blockquote = blockquote_re.search(data)
+        if blockquote:
+            blockquote = re.sub(r'^\n&gt; ?', '', blockquote.group(1))
+            blockquote = re.sub(r'\n&gt; ?', '\n', blockquote)
+            blockquote = re.sub(r'\n$', '', blockquote)
 
-            data = re.sub(r'(\n(?:&gt; ?(?:(?:(?!\n).)+)?\n)+)', '\n<blockquote>' + block + '</blockquote>\n', data, 1)
+            data = blockquote_re.sub('\n<blockquote>' + blockquote + '</blockquote>\n', data, 1)
         else:
             break
 
+    hr_re = re.compile(r'\n-{4,9}\n')
     while 1:
-        hr = re.search(r'\n-{4,9}\n', data)
-        if hr:
-            data = re.sub(r'\n-{4,9}\n', '\n<hr>\n', data, 1)
+        if hr_re.search(data):
+            data = hr_re.sub('\n<hr>\n', data, 1)
         else:
             break
 
     data = re.sub(r'(?P<in>\n +\* ?(?:(?:(?!\|\|).)+))\|\|', '\g<in>\n ||', data)
     data = re.sub(r'(?P<in><div id="folding_(?:[0-9]+)" style="display: none;"><div style="">|<blockquote>)(?P<out> )?\* ', '\g<in>\n\g<out>* ', data)
 
+    li_re = re.compile(r'(\n(?:(?: *)\* (?:[^\n]+)\n)+)')
     while 1:
-        li = re.search(r'(\n(?:(?: *)\* ?(?:(?:(?!\n).)+)\n)+)', data)
-        if li:
-            li = li.group(1)
-            while 1:
-                sub_li = re.search(r'\n(?:( *)\* ?((?:(?!\n).)+))', li)
-                if sub_li:
-                    sub_li = sub_li.groups()
-
-                    if len(sub_li[0]) == 0:
-                        margin = 20
-                    else:
-                        margin = len(sub_li[0]) * 20
+        li_data = li_re.search(data)
+        if li_data:
+            li_data = li_data.group(1)
+            li_end_data = ''
 
-                    li = re.sub(r'\n(?:( *)\* ?((?:(?!\n).)+))', '<li style="margin-left: ' + str(margin) + 'px;">' + sub_li[1] + '</li>', li, 1)
-                else:
-                    break
+            sub_li = re.findall(r'( *)\* ([^\n]+)\n', li_data)
+            for i in sub_li:
+                li_end_data += '<li style="margin-left: ' + str(20 if len(i[0]) == 0 else (len(i[0]) * 20)) + 'px;">' + i[1] + '</li>'
 
-            data = re.sub(r'(\n(?:(?: *)\* ?(?:(?:(?!\n).)+)\n)+)', '\n\n<ul>' + li + '</ul>\n', data, 1)
+            data = li_re.sub('\n\n<ul>' + li_end_data + '</ul>\n', data, 1)
         else:
             break
 
@@ -963,11 +920,7 @@ def namumark(conn, data, title, include_num):
     while 1:
         indent = re.search(r'\n( +)', data)
         if indent:
-            indent = len(indent.group(1))
-
-            margin = '<span style="margin-left: 20px;"></span>' * indent
-
-            data = re.sub(r'\n( +)', '\n' + margin, data, 1)
+            data = re.sub(r'\n( +)', '\n' + ('<span style="margin-left: 20px;"></span>' * len(indent.group(1))), data, 1)
         else:
             break
 
@@ -1031,10 +984,16 @@ def namumark(conn, data, title, include_num):
                 else:
                     file_color = ''
 
+                file_alt = re.search(r'alt=((?:(?!&).)+)', see_link)
+                if file_alt:
+                    file_alt = file_alt.group(1)
+                else:
+                    file_alt = ''
+
                 if re.search(r'^(?:out|외부):', main_link):
                     file_src = re.sub(r'^(?:out|외부):', '', main_link)
 
-                    file_alt = main_link
+                    file_alt = main_link if file_alt == '' else file_alt
                     exist = 'Yes'
                 else:
                     file_data = re.search(r'^(?:file|파일):((?:(?!\.).)+)\.(.+)$', main_link)
@@ -1050,7 +1009,7 @@ def namumark(conn, data, title, include_num):
                         file_end = 'jpg'
 
                     file_src = '/image/' + tool.sha224_replace(file_name) + '.' + file_end
-                    file_alt = 'file:' + file_name + '.' + file_end
+                    file_alt = ('file:' + file_name + '.' + file_end) if file_alt == '' else file_alt
                     exist = None
 
                 data = link_re.sub(
@@ -1067,32 +1026,33 @@ def namumark(conn, data, title, include_num):
                     1
                 )
             elif category_re.search(main_link):
-                if category == '':
-                    category += '<div id="cate_all"><hr><div id="cate">Category : '
-
-                main_link = category_re.sub('category:', main_link)
-                link_id = ''
-
-                curs.execute(tool.db_change("select title from data where title = ?"), [main_link])
-                if re.search(r'#blur', main_link):
-                    link_id = ' hidden_link'
-                    main_link = main_link.replace('#blur', '')
-                    see_link = see_link.replace('#blur', '')
-
-                backlink += [[title, main_link, 'cat']]
-                category += '' + \
-                    '<a class="' + include_name + 'link_finder' + link_id + '" ' + \
-                        'href="/w/' + tool.url_pas(main_link) + '">' + \
-                        category_re.sub('', see_link) + \
-                    '</a> | ' + \
-                ''
+                if include_name == '':
+                    if category == '':
+                        category += '<div id="cate_all"><div id="cate">Category : '
+
+                    main_link = category_re.sub('category:', main_link)
+                    link_id = ''
+
+                    curs.execute(tool.db_change("select title from data where title = ?"), [main_link])
+                    if re.search(r'#blur', main_link):
+                        link_id = ' hidden_link'
+                        main_link = main_link.replace('#blur', '')
+                        see_link = see_link.replace('#blur', '')
+
+                    backlink += [[title, main_link, 'cat']]
+                    category += '' + \
+                        '<a class="' + include_name + 'link_finder' + link_id + '" ' + \
+                            'href="/w/' + tool.url_pas(main_link) + '">' + \
+                            category_re.sub('', see_link) + \
+                        '</a> | ' + \
+                    ''
 
                 data = link_re.sub('', data, 1)
             elif re.search(r'^inter:((?:(?!:).)+):', main_link):
                 inter_data = re.search(r'^inter:((?:(?!:).)+):((?:(?!\]\]).)+)', main_link)
                 inter_data = inter_data.groups()
 
-                curs.execute(tool.db_change('select link, icon from inter where title = ?'), [inter_data[0]])
+                curs.execute(tool.db_change('select plus, plus_t from html_filter where html = ? and kind = "inter_wiki"'), [inter_data[0]])
                 inter = curs.fetchall()
                 if inter:
                     return_link = link_fix(inter_data[1], 1)
@@ -1217,14 +1177,12 @@ def namumark(conn, data, title, include_num):
     data = re.sub(r'\[clearfix\]', '<div style="clear:both"></div>', data, flags = re.I)
     data = re.sub(r'\[br\]', '<br>', data, flags = re.I)
 
+    # 각주 기능
     footnote_number = 0
-
     footnote_all = []
     footnote_dict = {}
     footnote_re = {}
-
-    footdata_all = '<hr><ul id="footnote_data">'
-
+    footdata_all = '<ul id="footnote_data">'
     re_footnote = re.compile(r'(?:\[\*((?:(?! |\]).)*)(?: ((?:(?!(?:\[\*|\])).)+))?\]|(\[(?:각주|footnote)\]))')
     while 1:
         footnote = re_footnote.search(data)
@@ -1251,7 +1209,7 @@ def namumark(conn, data, title, include_num):
                 data = re_footnote.sub(footdata_all + '</ul>', data, 1)
 
                 footnote_all = []
-                footdata_all = '<hr><ul id="footnote_data">'
+                footdata_all = '<ul id="footnote_data">'
             else:
                 footnote = footnote_data[1]
                 footnote_name = footnote_data[0]
@@ -1320,12 +1278,12 @@ def namumark(conn, data, title, include_num):
         ''
 
     footdata_all += '</ul>'
-    footdata_all = '</div>' + footdata_all
-    if footdata_all == '</div><hr><ul id="footnote_data"></ul>':
-        footdata_all = '</div>'
+    footdata_all = '' if footdata_all == '<ul id="footnote_data"></ul>' else footdata_all
+    footdata_all = '</div>' + footdata_all    
 
-    data = re.sub(r'\n$', footdata_all, data + '\n', 1)
+    data += footdata_all
 
+    # 기본 꾸미기 문법
     data = re.sub(r'&#x27;&#x27;&#x27;(?P<in>((?!&#x27;&#x27;&#x27;).)+)&#x27;&#x27;&#x27;', '<b>\g<in></b>', data)
     data = re.sub(r'&#x27;&#x27;(?P<in>((?!&#x27;&#x27;).)+)&#x27;&#x27;', '<i>\g<in></i>', data)
     data = re.sub(r'~~(?P<in>(?:(?!~~).)+)~~', '<s>\g<in></s>', data)
@@ -1334,11 +1292,8 @@ def namumark(conn, data, title, include_num):
     data = re.sub(r'\^\^(?P<in>(?:(?!\^\^).)+)\^\^', '<sup>\g<in></sup>', data)
     data = re.sub(r',,(?P<in>(?:(?!,,).)+),,', '<sub>\g<in></sub>', data)
 
-    if category != '':
-        category = re.sub(r' \| $', '', category) + '</div></div>'
-
-    data += category
-
+    # 최종 처리
+    data += (re.sub(r' \| $', '', category) + '</div></div>') if category != '' else ''
     data = data.replace('<no_table>', '||')
     data = data.replace('</td_1>', '</td>')
     data = re.sub(r'<\/ul>\n?', '</ul>', data)
@@ -1347,7 +1302,7 @@ def namumark(conn, data, title, include_num):
     data = data.replace('\n\n<ul>', '\n<ul>')
     data = data.replace('</ul>\n\n', '</ul>')
     data = re.sub(r'^(\n)+', '', data)
-    data = re.sub(r'(\n)+<hr><ul id="footnote_data">', '<hr><ul id="footnote_data">', data)
+    data = re.sub(r'(\n)+<ul id="footnote_data">', '<ul id="footnote_data">', data)
     data = re.sub(r'(?P<in><td(((?!>).)*)>)\n', '\g<in>', data)
     data = re.sub(r'(\n)?<hr>(\n)?', '<hr>', data)
     data = data.replace('</ul>\n\n<ul>', '</ul>\n<ul>')
@@ -1355,6 +1310,7 @@ def namumark(conn, data, title, include_num):
     data = data.replace('\n</ul>', '</ul>')
     data = data.replace('\n', '<br>')
 
+    # 파일, 링크, HTML 작동 JS
     plus_data += '' + \
         'get_link_state("' + include_name + '");\n' + \
         'get_file_state("' + include_name + '");\n' + \

+ 14 - 14
route/tool/set_mark/tool.py

@@ -21,21 +21,21 @@ def db_change(data):
 
 def ip_check(d_type = 0):
     ip = ''
-    if d_type == 0 and (flask.session and ('state' and 'id') in flask.session):
+    if d_type == 0 and (flask.session and 'id' in flask.session):
         ip = flask.session['id']
-    
-    if ip == '':
-        try:
-            ip = flask.request.environ.get('HTTP_X_REAL_IP', flask.request.environ.get('HTTP_X_FORWARDED_FOR', flask.request.remote_addr))
-            ip = ip[0] if type(ip) == type([]) else ip
-
-            if ip == '::1' or ip == '127.0.0.1':
-                ip = flask.request.environ.get('HTTP_X_FORWARDED_FOR', flask.request.remote_addr)
-                ip = ip[0] if type(ip) == type([]) else ip
-        except:
-            ip = 'error:ip'
-
-    return str(ip)
+    else:
+        ip_list = [
+            flask.request.environ.get('HTTP_X_REAL_IP', '::1'),
+            flask.request.environ.get('HTTP_X_FORWARDED_FOR', '::1'),
+            flask.request.environ.get('REMOTE_ADDR', '::1')
+        ]
+        for ip in ip_list:
+            if not (ip == '::1' or ip == '127.0.0.1'):
+                ip = ip[0] if type(ip) == type([]) else ip.split(',')[0]
+                
+                break
+
+    return ip
 
 def url_pas(data):
     return urllib.parse.quote(data).replace('/','%2F')

+ 4 - 3
route/user_custom_head_view.py

@@ -6,16 +6,17 @@ def user_custom_head_view_2(conn):
     ip = ip_check()
 
     if flask.request.method == 'POST':
+        get_data = flask.request.form.get('content', '')
         if ip_or_user(ip) == 0:
             curs.execute(db_change("select user from custom where user = ?"), [ip + ' (head)'])
             if curs.fetchall():
-                curs.execute(db_change("update custom set css = ? where user = ?"), [flask.request.form.get('content', None), ip + ' (head)'])
+                curs.execute(db_change("update custom set css = ? where user = ?"), [get_data, ip + ' (head)'])
             else:
-                curs.execute(db_change("insert into custom (user, css) values (?, ?)"), [ip + ' (head)', flask.request.form.get('content', None)])
+                curs.execute(db_change("insert into custom (user, css) values (?, ?)"), [ip + ' (head)', get_data])
 
             conn.commit()
 
-        flask.session['head'] = flask.request.form.get('content', None)
+        flask.session['head'] = get_data
 
         return redirect('/custom_head')
     else:

+ 41 - 9
route/user_setting.py

@@ -11,15 +11,30 @@ def user_setting_2(conn, server_init):
 
     if ip_or_user(ip) == 0:
         if flask.request.method == 'POST':
-            auto_list = ['email', 'skin', 'lang']
+            pass_list = ['2fa']
+            auto_list = ['skin', 'lang'] + pass_list + ['2fa_pw', '2fa_pw_encode']
 
             for auto_data in auto_list:
-                if flask.request.form.get(auto_data, '') != '':
+                if auto_data == '2fa_pw':
+                    if flask.request.form.get('2fa_pw', '') != '':
+                        get_data = pw_encode(flask.request.form.get(auto_data, ''))
+                    else:
+                        get_data = ''
+                elif auto_data == '2fa_pw_encode':
+                    if flask.request.form.get('2fa_pw', '') != '':
+                        curs.execute(db_change("select encode from user where id = ?"), [ip])
+                        get_data = curs.fetchall()[0][0]
+                    else:
+                        get_data = ''
+                else:
+                    get_data = flask.request.form.get(auto_data, '')
+
+                if auto_data in pass_list or get_data != '':
                     curs.execute(db_change('select data from user_set where name = ? and id = ?'), [auto_data, ip])
                     if curs.fetchall():
-                        curs.execute(db_change("update user_set set data = ? where name = ? and id = ?"), [flask.request.form.get(auto_data, ''), auto_data, ip])
+                        curs.execute(db_change("update user_set set data = ? where name = ? and id = ?"), [get_data, auto_data, ip])
                     else:
-                        curs.execute(db_change("insert into user_set (name, id, data) values (?, ?, ?)"), [auto_data, ip, flask.request.form.get(auto_data, '')])
+                        curs.execute(db_change("insert into user_set (name, id, data) values (?, ?, ?)"), [auto_data, ip, get_data])
 
             conn.commit()
 
@@ -41,23 +56,36 @@ def user_setting_2(conn, server_init):
                 data = [['default']]
 
             for lang_data in support_language:
+                see_data = lang_data if lang_data != 'default' else load_lang('default')
+                
                 if data and data[0][0] == lang_data:
-                    div3 = '<option value="' + lang_data + '">' + lang_data + '</option>' + div3
+                    div3 = '<option value="' + lang_data + '">' + see_data + '</option>' + div3
                 else:
-                    div3 += '<option value="' + lang_data + '">' + lang_data + '</option>'
+                    div3 += '<option value="' + lang_data + '">' + see_data + '</option>'
 
-            http_warring = '<hr class="main_hr"><span>' + load_lang('http_warring') + '</span>'
+            curs.execute(db_change('select data from user_set where name = "2fa" and id = ?'), [ip])
+            fa_data = curs.fetchall()
+            fa_data = 'checked' if fa_data and fa_data[0][0] != '' else ''
+
+            curs.execute(db_change('select data from user_set where name = "2fa_pw" and id = ?'), [ip])
+            fa_data_pw = curs.fetchall()
+            fa_data_pw = load_lang('2fa_password_change') if fa_data_pw else load_lang('2fa_password')
+            
+            http_warring = '' + \
+                '<hr class="main_hr">' + \
+                '<span>' + load_lang('http_warring') + '</span>' + \
+            ''
 
             return easy_minify(flask.render_template(skin_check(),
                 imp = [load_lang('user_setting'), wiki_set(), custom(), other2([0, 0])],
                 data = '''
                     <form method="post">
-                        <span>''' + load_lang('id') + ''' : ''' + ip + '''</span>
+                        <span>''' + load_lang('id') + ''' : ''' + ip_pas(ip) + '''</span>
                         <hr class="main_hr">
                         <a href="/pw_change">(''' + load_lang('password_change') + ''')</a>
                         <hr class="main_hr">
                         <span>''' + load_lang('email') + ''' : ''' + email + '''</span> <a href="/email_change">(''' + load_lang('email_change') + ''')</a>
-                        <hr class="main_hr">
+                        <h2>''' + load_lang('main') + '''</h2>
                         <span>''' + load_lang('skin') + '''</span>
                         <hr class="main_hr">
                         <select name="skin">''' + div2 + '''</select>
@@ -65,6 +93,10 @@ def user_setting_2(conn, server_init):
                         <span>''' + load_lang('language') + '''</span>
                         <hr class="main_hr">
                         <select name="lang">''' + div3 + '''</select>
+                        <h2>''' + load_lang('2fa') + '''</h2>
+                        <input type="checkbox" name="2fa" value="on" ''' + fa_data + '''> ''' + load_lang('on') + '''
+                        <hr class="main_hr">
+                        <input type="password" name="2fa_pw" placeholder="''' + fa_data_pw + '''">
                         <hr class="main_hr">
                         <button type="submit">''' + load_lang('save') + '''</button>
                         ''' + http_warring + '''

+ 2 - 5
route/view_read.py

@@ -11,9 +11,6 @@ def view_read_2(conn, name):
     num = flask.request.args.get('num', None)
     if num:
         num = int(number_check(num))
-    else:
-        if not flask.request.args.get('from', None):
-            run_redirect = '<script>not_from_exist();</script>'
 
     curs.execute(db_change("select sub from rd where title = ? and not stop = 'O' order by date desc"), [name])
     if curs.fetchall():
@@ -124,7 +121,7 @@ def view_read_2(conn, name):
 
         curs.execute(db_change('' + \
             'select ip, date, leng, send, id from history ' + \
-            'where title = ? and hide != "O" and type = "" order by id desc limit 3' + \
+            'where title = ? and hide != "O" and type = "" order by id + 0 desc limit 3' + \
         ''), [name])
         sql_d = curs.fetchall()
         if sql_d:
@@ -210,4 +207,4 @@ def view_read_2(conn, name):
         imp = [flask.request.args.get('show', name), wiki_set(), custom(), other2([sub, r_date, watch_list])],
         data = div,
         menu = menu
-    )), response_data
+    )), response_data

+ 41 - 0
route/vote.py

@@ -0,0 +1,41 @@
+from .tool.func import *
+
+def vote_2(conn):
+    curs = conn.cursor()
+
+    sql_num_1 = int(number_check(flask.request.args.get('num', '1')))
+    sql_num = (sql_num_1 * 50 - 50) if sql_num_1 * 50 > 0 else 0
+
+    data = ''
+    if flask.request.args.get('close', 'n') == 'n':
+        data += '<a href="/vote?close=y">(' + load_lang('close_vote_list') + ')</a>'
+        sub = 0
+        curs.execute(db_change('select name, id, type from vote where type = "open" or type = "n_open" limit ?, 50'), [sql_num])
+    else:
+        data += '<a href="/vote">(' + load_lang('open_vote_list') + ')</a>'
+        sub = '(' + load_lang('closed') + ')'
+        curs.execute(db_change('select name, id, type from vote where type = "close" or type = "n_close" limit ?, 50'), [sql_num])
+
+    data += '<ul>'
+
+    data_list = curs.fetchall()
+    for i in data_list:
+        if flask.request.args.get('close', 'n') == 'n':
+            open_select = load_lang('open_vote') if i[2] == 'open' else load_lang('not_open_vote')
+        else:
+            open_select = load_lang('open_vote') if i[2] == 'close' else load_lang('not_open_vote')
+
+        data += '<li><a href="/vote/' + i[1] + '">' + html.escape(i[0]) + ' (' + open_select + ')</a></li>'
+
+    data += '</ul>'
+    if flask.request.args.get('close', 'n') == 'n':
+        data += ('<a href="/add_vote">(' + load_lang('add_vote') + ')</a>') if admin_check() == 1 else ''
+        data += next_fix('/vote?num=', sql_num_1, data_list)
+    else:
+        data += next_fix('/vote?close=y&num=', sql_num_1, data_list)
+
+    return easy_minify(flask.render_template(skin_check(),
+        imp = [load_lang('vote_list'), wiki_set(), custom(), other2([sub, 0])],
+        data = data,
+        menu = [['other', load_lang('return')]]
+    ))

+ 62 - 0
route/vote_add.py

@@ -0,0 +1,62 @@
+from .tool.func import *
+
+def vote_add_2(conn):
+    curs = conn.cursor()
+
+    if admin_check() != 1:
+        return re_error('/ban')
+
+    if flask.request.method == 'POST':
+        vote_data = flask.request.form.get('data', 'test\ntest_2')
+        if vote_data.count('\n') < 1:
+            return re_error('/ban')
+
+        curs.execute(db_change('select id from vote order by id + 0 desc limit 1'))
+        id_data = curs.fetchall()
+        id_data = str((int(id_data[0][0]) + 1) if id_data else 1)
+
+        admin_check(None, 'add vote ' + id_data)
+
+        if flask.request.form.get('open_select', 'N') == 'Y':
+            open_data = 'open'
+        else:
+            open_data = 'n_open'
+
+        curs.execute(db_change("insert into vote (name, id, subject, data, user, type, acl) values (?, ?, ?, ?, '', ?, ?)"), [
+            flask.request.form.get('name', 'test'),
+            id_data,
+            flask.request.form.get('subject', 'test'),
+            flask.request.form.get('data', 'test'),
+            open_data,
+            flask.request.form.get('acl_select', '')
+        ])
+        conn.commit()
+
+        return redirect('/vote')
+    else:
+        acl_data = '<select name="acl_select">'
+        acl_list = get_acl_list()
+        for data_list in acl_list:
+            acl_data += '<option value="' + data_list + '">' + (data_list if data_list != '' else 'normal') + '</option>'
+
+        acl_data += '</select>'
+
+        return easy_minify(flask.render_template(skin_check(),
+            imp = [load_lang('add_vote'), wiki_set(), custom(), other2([0, 0])],
+            data = '' + \
+                '<form method="post">' + \
+                    '<input name="name" placeholder="' + load_lang('name') + '">' + \
+                    '<hr class="main_hr">' + \
+                    '<textarea rows="3" name="subject" placeholder="' + load_lang('explanation') + '"></textarea>' + \
+                    '<hr class="main_hr">' + \
+                    '<textarea rows="10" name="data" placeholder="' + load_lang('1_line_1_q') + '"></textarea>' + \
+                    '<hr class="main_hr">' + \
+                    '<input type="checkbox" value="Y" name="open_select"> ' + load_lang('open_vote') + \
+                    '<h2>' + load_lang('acl') + '</h2>' + \
+                    acl_data + ' <a href="/acl/TEST#exp">(' + load_lang('explanation') + ')</a>' + \
+                    '<hr class="main_hr">' + \
+                    '<button type="submit">' + load_lang('send') + '</buttom>' + \
+                '</form>' + \
+            '',
+            menu = [['vote', load_lang('return')]]
+        ))

+ 29 - 0
route/vote_close.py

@@ -0,0 +1,29 @@
+from .tool.func import *
+
+def vote_close_2(conn, num):
+    curs = conn.cursor()
+
+    if admin_check() != 1:
+        return re_error('/ban')
+
+    curs.execute(db_change('select type from vote where id = ? and user = ""'), [num])
+    data_list = curs.fetchall()
+    if not data_list:
+        return redirect('/vote')
+        
+    if data_list[0][0] == 'close':
+        type_set = 'open'
+    elif data_list[0][0] == 'n_close':
+        type_set = 'n_open'
+    elif data_list[0][0] == 'open':
+        type_set = 'close'
+    else:
+        type_set = 'n_close'
+        
+    curs.execute(db_change("update vote set type = ? where user = '' and id = ?"), [type_set, num])
+    conn.commit()
+
+    if data_list[0][0] == 'close' or data_list[0][0] == 'n_close':
+        return redirect('/vote')
+    else:
+        return redirect('/vote?close=y')

+ 47 - 0
route/vote_end.py

@@ -0,0 +1,47 @@
+from .tool.func import *
+
+def vote_end_2(conn, num):
+    curs = conn.cursor()
+
+    curs.execute(db_change('select name, subject, data, type from vote where id = ? and user = ""'), [num])
+    data_list = curs.fetchall()
+    if not data_list:
+        return redirect('/vote')
+
+    data = '' + \
+        '<h2>' + data_list[0][0] + '</h2>' + \
+        '<b>' + data_list[0][1] + '</b>' + \
+    ''
+
+    if admin_check() == 1:
+        if data_list[0][3] == 'open' or data_list[0][3] == 'n_open':
+            data += '' + \
+                '<hr class="main_hr">' + \
+                '<a href="/close_vote/' + num + '">(' + load_lang('close_vote') + ')</a>' + \
+            ''
+        else:
+            data += '' + \
+                '<hr class="main_hr">' + \
+                '<a href="/close_vote/' + num + '">(' + load_lang('re_open_vote') + ')</a>' + \
+            ''
+
+    vote_data = re.findall(r'([^\n]+)', data_list[0][2].replace('\r\n', '\n'))
+    for i in range(0, len(vote_data)):
+        data += '<h2>' + vote_data[i] + '</h2>'
+        data += '<ul>'
+        
+        curs.execute(db_change('select user from vote where id = ? and user != "" and data = ?'), [num, str(i)])
+        data_list_2 = curs.fetchall()
+        if data_list[0][3] == 'open' or data_list[0][3] == 'close':
+            all_ip = ip_pas([j[0] for j in data_list_2])
+            for j in data_list_2:
+                data += '<li>' + all_ip[j[0]] + '</li>'
+
+        data += '<li>' + load_lang('result') + ' : ' + str(len(data_list_2)) + '</li>'
+        data += '</ul>'
+
+    return easy_minify(flask.render_template(skin_check(),
+        imp = [load_lang('result_vote'), wiki_set(), custom(), other2([0, 0])],
+        data = data,
+        menu = [['vote', load_lang('return')]]
+    ))

+ 66 - 0
route/vote_select.py

@@ -0,0 +1,66 @@
+from .tool.func import *
+
+def vote_select_2(conn, num):
+    curs = conn.cursor()
+
+    curs.execute(db_change('select name, subject, data, type from vote where id = ? and user = ""'), [num])
+    data_list = curs.fetchall()
+    if not data_list:
+        return redirect('/vote')
+
+    if data_list[0][3] == 'close' or data_list[0][3] == 'n_close':
+        return redirect('/end_vote/' + num)
+
+    if acl_check('', 'vote', num) == 1:
+        return redirect('/end_vote/' + num)
+
+    curs.execute(db_change('select user from vote where id = ? and user = ?'), [num, ip_check()])
+    if curs.fetchall():
+        return redirect('/end_vote/' + num)
+
+    vote_data = re.findall(r'([^\n]+)', data_list[0][2].replace('\r\n', '\n'))
+
+    if flask.request.method == 'POST':
+        try:
+            vaild_check = int(flask.request.form.get('vote_data', '0'))
+        except:
+            return redirect('/vote/' + num)
+
+        if len(vote_data) - 1 < vaild_check:
+            return redirect('/vote/' + num)
+
+        curs.execute(db_change("insert into vote (name, id, subject, data, user, type) values ('', ?, '', ?, ?, 'select')"), [
+            num,
+            str(vaild_check),
+            ip_check()
+        ])
+        conn.commit()
+
+        return redirect('/end_vote/' + num)
+    else:
+        data = '' + \
+            '<h2>' + data_list[0][0] + '</h2>' + \
+            '<b>' + data_list[0][1] + '</b>' + \
+            '<hr class="main_hr">' + \
+        ''
+
+        select_data = '<select name="vote_data">'
+        line_num = 0
+        for i in vote_data:
+            select_data += '<option value="' + str(line_num) + '">' + i + '</option>'
+            line_num += 1
+
+        select_data += '</select>'
+        data += '' + \
+            '<form method="post">' + \
+                select_data + \
+                '<hr class="main_hr">' + \
+                '<button type="submit">' + load_lang('send') + '</buttom>' + \
+            '</form>' + \
+        ''
+
+        return easy_minify(flask.render_template(skin_check(),
+            imp = [load_lang('vote'), wiki_set(), custom(), other2([0, 0])],
+            data = data,
+            menu = [['vote', load_lang('return')], ['end_vote/' + num, load_lang('result')]]
+        ))

+ 5 - 9
version.json

@@ -1,11 +1,7 @@
 {
-    "master" : {
-        "r_ver" : "v3.2.0-stable-11",
-        "c_ver" : "3200900",
-        "s_ver" : "9"
-    }, "stable" : {
-        "r_ver" : "v3.2.0-stable-11",
-        "c_ver" : "3200900",
-        "s_ver" : "9"
+    "beta" : {
+        "r_ver" : "v3.2.0-stable-12 (beta-31) (dev-2020-09-04-01)",
+        "c_ver" : "3202600",
+        "s_ver" : "10"
     }
-}
+}

+ 3 - 1
views/main_css/css/main.css

@@ -18,7 +18,7 @@ input[type="checkbox"], input[type="radio"] { width: auto; }
 #toron_color_red { background: #fecabf; }
 #toron_color_grey { background: gainsboro; }
 #toron_color_not { display: none; }
-#cate { border: 1px solid; padding: 5px; }
+#cate { border: 1px solid; padding: 5px; margin-top: 20px; }
 blockquote { border: 1px solid; padding: 15px; margin: 0; margin-top: 10px; display: inline-block; }
 img, iframe { max-width: 100%; }
 pre { border: 1px solid; padding: 10px; white-space: pre-wrap; }
@@ -49,3 +49,5 @@ blockquote { background-image: url(/views/main_css/file/quote.png); background-p
 .content { height: 500px; }
 .topic_content { height: 200px; }
 .spead_footnote { background-color: #efefef; color: #555; border: 1px solid #cecece; }
+#footnote_data { border-top: 1px solid gainsboro; padding-top: 10px; }
+a { color: dodgerblue; }

+ 12 - 0
views/main_css/css/sub/dark.css

@@ -0,0 +1,12 @@
+html { background: black; color: white; }
+textarea, input, button, select { background: #1f2023; color: white; }
+input::placeholder, textarea::placeholder, select::placeholder { color: white; }
+#toc, #cate, #redirect { background: #1f2023; }
+#toron_color_grey { background: #4a4a4a; }
+#toron_color_green { background: #2e4a2e; }
+#toron_color_red { background: #803737; }
+#toron_color_blue { background: #314c56; }
+pre#syntax, pre#syntax code { background: black; }
+.hljs, .hljs-subst { color: white; }
+blockquote { background-color: black; border-left: 5px solid #eee; }
+.spead_footnote { background-color: black; color: white; }

+ 0 - 16
views/main_css/js/load_namumark.js

@@ -134,22 +134,6 @@ function page_count() {
     }
 }
 
-function not_from_exist() {
-    window.addEventListener('DOMContentLoaded', function() {
-        if(document.getElementById('go_redirect_link')) {
-            var r_link = document.getElementById('go_redirect_link').href;
-            if(r_link.match(/#([^#]+)$/)) {
-                var s_link = '#' + r_link.match(/#([^#]+)$/)[1];
-                r_link = r_link.replace(/#([^#]+)$/, '');
-            } else {
-                var s_link = '';
-            }
-
-            window.location.href = r_link + '?from=' + location.pathname.replace(/^\/w\//, '') + s_link;
-        }
-    });
-}
-
 function do_open_folding(data, element = '') {
     var fol = document.getElementById(data);
     if(fol.style.display === '' || (fol.style.display === 'inline-block' || fol.style.display === 'block')) {

+ 1 - 3
views/main_css/js/load_skin_set.js

@@ -93,10 +93,8 @@ function main_css_skin_load() {
             var backup_category = get_category.innerHTML;
             var in_data = document.getElementById('in_data_0').innerHTML;
             get_category.innerHTML = '';
-
-            backup_category = backup_category.replace('<hr>', '') + '<hr>';
-
             document.getElementById('in_data_0').innerHTML = backup_category + in_data;
+            head_data.innerHTML += '<style>#cate { margin-top: 0px; margin-bottom: 20px; }</style>';
         }
     }
 }

+ 4 - 31
views/marisa/css/dark.css

@@ -17,46 +17,19 @@ html, #main {
     color: white;
 }
 
-textarea, input, button, select {
-    background: #1f2023;
-    color: white;
-    border-color: #1f2023;
-}
-
 #mobile_search_input, #mobile_search_input::placeholder {
     background: gray;
     color: white;
 }
 
-#toron_color_grey {
-    background: #4a4a4a;
-}
-
-#toron_color_green {
-    background: #2e4a2e;
-}
-
-#toron_color_red {
-    background: #803737;
-}
-
-#toron_color_blue {
-    background: #314c56;
-}
-
-pre#syntax {
-    background: black;
-}
-
 a#titlt_a {
     color: white;
 }
 
-button#save {
-    background: #083808;
+textarea, input, button, select, #toc, #cate, #redirect { 
+    border-color: #1f2023; 
 }
 
-blockquote {
-    background-color: black;
-    border-left: 5px solid #eee;
+button#save {
+    background: #083808;
 }

+ 17 - 7
views/marisa/index.html

@@ -9,7 +9,7 @@
         {% endif %}
         {{imp[3][3]|safe}}
         <link rel="stylesheet" href="/views/marisa/css/main.css?ver=16">
-        <script src="/views/marisa/js/skin_set.js?ver=4"></script>
+        <script src="/views/marisa/js/skin_set.js?ver=5"></script>
         <script src="/views/marisa/js/main.js?ver=3"></script>
         <script>main_load(); window.addEventListener('DOMContentLoaded', function() { skin_set(); });</script>
         <script src="https://code.iconify.design/1/1.0.3/iconify.min.js"></script>
@@ -37,18 +37,23 @@
                             <div id="top_tool_cel">
                                 <a href="javascript:void(0);" onclick="opening('recent_cel');">
                                     <span class="iconify" data-icon="ic:baseline-access-time" data-inline="true"></span>
-                                    <span class="not_mobile">{{'recent'|load_lang}}</span>
+                                    <span class="not_mobile">{{'list'|load_lang}}</span>
                                     <span class="iconify" data-icon="ic:baseline-arrow-drop-down" data-inline="true"></span>
                                 </a>
                                 <div id="recent_cel" class="cel_in_cel" style="display: none;">
                                     <a href="/recent_changes">
                                         <span class="iconify" data-icon="ic:baseline-autorenew" data-inline="true"></span>
-                                        {{'edit'|load_lang}}
+                                        {{'recent_change'|load_lang}}
                                     </a>
                                     <hr>
                                     <a href="/recent_discuss">
                                         <span class="iconify" data-icon="ic:baseline-add-comment" data-inline="true"></span>
-                                        {{'discussion'|load_lang}}
+                                        {{'recent_discussion'|load_lang}}
+                                    </a>
+                                    <hr>
+                                    <a href="/vote">
+                                        <span class="iconify" data-icon="ic:baseline-how-to-vote" data-inline="true"></span>
+                                        {{'vote_list'|load_lang}}
                                     </a>
                                 </div>
                             </div>
@@ -56,7 +61,7 @@
                             <div id="top_tool_cel">
                                 <a href="javascript:void(0);" onclick="opening('other_cel');">
                                     <span class="iconify" data-icon="ic:baseline-archive" data-inline="true"></span>
-                                    <span class="not_mobile">{{'other'|load_lang}}</span>
+                                    <span class="not_mobile">{{'tool'|load_lang}}</span>
                                     <span class="iconify" data-icon="ic:baseline-arrow-drop-down" data-inline="true"></span>
                                 </a>
                                 <div id="other_cel" class="cel_in_cel" style="display: none;">
@@ -67,16 +72,21 @@
                                     <hr>
                                     <a href="/other">
                                         <span class="iconify" data-icon="ic:baseline-build" data-inline="true"></span>
-                                        {{'tool'|load_lang}}
+                                        {{'other_tool'|load_lang}}
                                     </a>
                                     {% if imp[2][9] != '0' %}
                                         <hr>
                                         <a href="/manager">
                                             <span class="iconify" data-icon="ic:baseline-how-to-reg" data-inline="true"></span>
-                                            {{'admin'|load_lang}}
+                                            {{'admin_tool'|load_lang}}
                                         </a>
                                     {% endif %}
                                     <hr>
+                                    <a href="/upload">
+                                        <span class="iconify" data-icon="ic:baseline-cloud-upload" data-inline="true"></span>
+                                        {{'upload'|load_lang}}
+                                    </a>
+                                    <hr>
                                     <a href="/skin_set">
                                         <span class="iconify" data-icon="ic:baseline-settings" data-inline="true"></span>
                                         {{'skin_setting'|load_lang}}

+ 2 - 2
views/marisa/info.json

@@ -1,5 +1,5 @@
 {
     "name" : "Marisa",
-    "skin_ver" : "v1.2.6",
-    "require_ver" : "9"
+    "skin_ver" : "v1.2.8",
+    "require_ver" : "10"
 }

+ 4 - 1
views/marisa/js/skin_set.js

@@ -15,7 +15,10 @@ function main_load() {
         cookies.match(regex_data('invert')) &&
         cookies.match(regex_data('invert'))[1] === '1'
     ) {
-        head_data.innerHTML += '<link rel="stylesheet" href="/views/marisa/css/dark.css?ver=6">';
+        head_data.innerHTML += '' +
+            '<link rel="stylesheet" href="/views/main_css/css/sub/dark.css?ver=1">' +
+            '<link rel="stylesheet" href="/views/marisa/css/dark.css?ver=6">' +
+        '';
     }
 }