Преглед изворни кода

Merge pull request #1894 from openNAMU/beta

RC4 업데이트
잉여개발기 (SPDV) пре 2 година
родитељ
комит
0a36191a7f

+ 9 - 1
app.py

@@ -456,6 +456,8 @@ app.route('/change/head/<skin_name>', methods=['GET', 'POST'])(user_setting_head
 app.route('/change/head_reset', methods=['GET', 'POST'])(user_setting_head_reset)
 app.route('/change/skin_set')(user_setting_skin_set)
 app.route('/change/top_menu', methods=['GET', 'POST'])(user_setting_top_menu)
+app.route('/change/user_name', methods=['GET', 'POST'])(user_setting_user_name)
+app.route('/change/user_name/<user_name>', methods=['GET', 'POST'])(user_setting_user_name)
 # 하위 호환용 S
 app.route('/skin_set')(user_setting_skin_set)
 # 하위 호환용 E
@@ -524,10 +526,15 @@ app.route('/bbs/set/<int:bbs_num>', methods = ['POST', 'GET'])(bbs_w_set)
 app.route('/bbs/edit/<int:bbs_num>', methods = ['POST', 'GET'])(bbs_w_edit)
 app.route('/bbs/edit/preview/<int:bbs_num>', methods = ['POST', 'GET'], defaults = { 'do_type' : 'preview' })(bbs_w_edit)
 app.route('/bbs/w/<int:bbs_num>/<int:post_num>', methods = ['POST', 'GET'])(bbs_w_post)
+app.route('/bbs/raw/<int:bbs_num>/<int:post_num>')(view_raw_2)
+app.route('/bbs/tool/<int:bbs_num>/<int:post_num>')(bbs_w_tool)
 app.route('/bbs/edit/<int:bbs_num>/<int:post_num>', methods = ['POST', 'GET'])(bbs_w_edit)
 app.route('/bbs/edit/preview/<int:bbs_num>/<int:post_num>', methods = ['POST', 'GET'], defaults = { 'do_type' : 'preview' })(bbs_w_edit)
 app.route('/bbs/w/preview/<int:bbs_num>/<int:post_num>', methods = ['POST'], defaults = { 'do_type' : 'preview' })(bbs_w_post)
-# app.route('/bbs/edit/<int:bbs_num>/<int:post_num>')
+app.route('/bbs/tool/<int:bbs_num>/<int:post_num>/<comment_num>')(bbs_w_comment_tool)
+app.route('/bbs/raw/<int:bbs_num>/<int:post_num>/<comment_num>')(view_raw_2)
+app.route('/bbs/edit/<int:bbs_num>/<int:post_num>/<comment_num>', methods = ['POST', 'GET'])(bbs_w_edit)
+app.route('/bbs/edit/preview/<int:bbs_num>/<int:post_num>/<comment_num>', methods = ['POST', 'GET'])(bbs_w_edit)
 
 # Func-api
 app.route('/api/w/<everything:name>/doc_tool/<tool>/doc_rev/<int(signed = True):rev>')(api_w)
@@ -537,6 +544,7 @@ app.route('/api/raw/<everything:name>')(api_raw)
 
 app.route('/api/bbs/w/<sub_code>')(api_bbs_w_post)
 app.route('/api/bbs/w/comment/<sub_code>')(api_bbs_w_comment)
+app.route('/api/bbs/w/comment_one/<sub_code>')(api_bbs_w_comment)
 
 app.route('/api/version', defaults = { 'version_list' : version_list })(api_version)
 app.route('/api/skin_info')(api_skin_info)

+ 15 - 2
lang/en-US.json

@@ -61,6 +61,7 @@
         "check" : "Check",
         "destruction" : "Destruction",
         "tool" : "Tool",
+        "position" : "Position",
         "recent" : "Recently",
         "password" : "Password",
         "login" : "Sign in",
@@ -240,6 +241,8 @@
         "user_added_menu" : "User added menu",
         "move_redirect_make" : "Redirect document generation (Only if possible)",
         "user_fix" : "Fix user",
+        "main_user_name" : "Main user name",
+        "change_user_name" : "Change main user name",
         "sub_user_name" : "Sub user name",
         "_comment_" : "BBS",
             "bbs" : "BBS",
@@ -254,6 +257,10 @@
             "reply" : "Reply",
             "post_edit" : "Modify post",
             "post_add" : "Add post",
+            "last_comment_time" : "last comment time",
+            "bbs_comment_tool" : "BBS comment tool(s)",
+            "bbs_comment_edit" : "BBS comment edit",
+            "bbs_post_tool" : "BBS post tool(s)",
             "_comment_" : "BBS ACL",
                 "bbs_view_acl" : "BBS ACL to view posts",
                 "bbs_acl" : "BBS ACL",
@@ -286,6 +293,8 @@
                 "footnote_number" : "Footnote number output",
                 "only_number" : "Only number",
                 "footnote_real_num_view" : "Footnote original number view",
+                "category_change_title" : "Don't rename documents in category",
+                "table_scroll" : "Use scroll in tables",
             "_comment_" : "Option",
                 "change_to_normal" : "Change to plain text.",
                 "change_to_link" : "Change to Link.",
@@ -378,6 +387,9 @@
                 "set_history_recording_off" : "Stop recording history",
                 "link_case_insensitive" : "Link case insensitive",
                 "hide_user_name" : "Hide member name",
+                "move_with_redirect" : "Create a redirect when moving a document",
+                "slow_thread" : "Continuous thread upload limit",
+                "edit_timeout" : "render timeout",
             "_comment_" : "Text",
                 "register_text" : "Terms of sign-up",
                 "non_login_alert" : "Non-login alert",
@@ -536,6 +548,7 @@
         "many_delete_help" : "Please write down the documents name one by one on the line.",
         "name_or_ip_or_regex_multiple" : "Please write down the username or IP or Regex one by one on the line.",
         "sqlite_only" : "SQLite only",
+        "linux_only" : "Linux OS only",
         "approval_question_visible_only_when_approval_on" : "Approval questions are visible only when approval requirement is on",
         "msg_whatchlist_lmt": "You can add as many as",
         "enter_html" : "Please enter HTML",
@@ -552,8 +565,8 @@
             "error_skin_set" : "The skin you are using does not support individual settings.",
             "error_skin_set_old" : "Some older skins may only work by using the old version link.",
             "same_id_exist_error" : "There are users using the same username.",
-            "long_id_error" : "Username must be shorter than 20 characters.",
-            "id_char_error" : "Only Korean letters and alphabets are allowed for Username.",
+            "long_id_error" : "Username must be shorter than 32 characters.",
+            "id_char_error" : "Only Korean letters and alphabets and numbers are allowed for Username.",
             "file_exist_error" : "The file does not exist.",
             "password_error" : "Password does not match.",
             "recaptcha_error" : "Pass the reCAPTCHA.",

+ 17 - 4
lang/ko-KR.json

@@ -208,7 +208,7 @@
     "name": "이름",
     "period": "기간",
     "writer": "작성자",
-    "long_id_error": "ID는 20자보다 짧아야 합니다.",
+    "long_id_error": "ID는 32자보다 짧아야 합니다.",
     "edit_record_error": "사유는 500자를 넘을 수 없습니다.",
     "revert": "복원",
     "discussion_authority": "토론 관리 권한",
@@ -231,7 +231,7 @@
     "file_name": "파일명",
     "close_discussion": "닫힌 토론",
     "language": "언어",
-    "id_char_error": "오직 한글과 알파벳만 사용 가능합니다.",
+    "id_char_error": "오직 한글과 알파벳, 숫자만 사용 가능합니다.",
     "id_filter_add": "ID 필터 추가",
     "skin": "스킨",
     "user_head": "사용자 <HEAD>",
@@ -553,6 +553,19 @@
     "footnote_number": "각주 번호 출력",
     "only_number": "숫자만",
     "footnote_real_num_view": "각주 실제 번호 보기",
-    "post_edit" : "게시글 수정",
-    "post_add" : "게시글 추가"
+    "post_edit": "게시글 수정",
+    "post_add": "게시글 추가",
+    "main_user_name": "메인 사용자명",
+    "change_user_name": "메인 사용자명 변경",
+    "last_comment_time": "마지막 댓글 시간",
+    "bbs_comment_tool": "BBS 댓글 도구",
+    "bbs_comment_edit": "BBS 댓글 수정",
+    "move_with_redirect": "문서 이동시 리다이렉트 문서 생성",
+    "slow_thread": "토론 올리기 제한 시간",
+    "edit_timeout": "렌더링 제한 시간",
+    "linux_only": "리눅스 OS 전용",
+    "bbs_post_tool": "BBS 게시글 도구",
+    "position": "위치",
+    "category_change_title": "분류 내 문서명 변경 금지",
+    "table_scroll": "표에 스크롤 사용"
 }

+ 4 - 0
route/__init__.py

@@ -14,6 +14,7 @@ from route.api_version import api_version
 from route.api_w import api_w
 from route.api_bbs_w_post import api_bbs_w_post
 from route.api_bbs_w_comment import api_bbs_w_comment
+from route.api_bbs_w_comment_one import api_bbs_w_comment_one
 
 from route.bbs_w_edit import bbs_w_edit
 from route.bbs_main import bbs_main
@@ -21,6 +22,8 @@ from route.bbs_make import bbs_make
 from route.bbs_w import bbs_w
 from route.bbs_w_post import bbs_w_post
 from route.bbs_w_set import bbs_w_set
+from route.bbs_w_comment_tool import bbs_w_comment_tool
+from route.bbs_w_tool import bbs_w_tool
 
 from route.edit import edit
 from route.edit_backlink_reset import edit_backlink_reset
@@ -146,6 +149,7 @@ from route.user_setting_pw import user_setting_pw
 from route.user_setting_skin_set import user_setting_skin_set
 from route.user_setting_skin_set_main import user_setting_skin_set_main
 from route.user_setting_top_menu import user_setting_top_menu
+from route.user_setting_user_name import user_setting_user_name
 
 from route.user_watch_list import user_watch_list
 from route.user_watch_list_name import user_watch_list_name

+ 30 - 0
route/api_bbs_w_comment_one.py

@@ -0,0 +1,30 @@
+from .tool.func import *
+
+def api_bbs_w_comment_one(sub_code = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+        
+        sub_code = sub_code.split('-')
+        sub_code_last = ''
+        if len(sub_code) > 2:
+            sub_code_last = sub_code[len(sub_code) - 1]
+            del sub_code[len(sub_code) - 1]
+            
+        sub_code = '-'.join(sub_code)
+
+        curs.execute(db_change('select set_name, set_data, set_code, set_id from bbs_data where (set_name = "comment" or set_name = "comment_date" or set_name = "comment_user_id") and set_id = ? and set_code = ?'), [sub_code, sub_code_last])
+        db_data = curs.fetchall()
+        if not db_data:
+            return flask.jsonify({})
+        else:
+            temp_id = ''
+            temp_dict = {}
+
+            for for_a in db_data:
+                if temp_id != for_a[2]:
+                    temp_id = for_a[2]
+                    temp_dict['code'] = for_a[2]
+
+                temp_dict[for_a[0]] = for_a[1]
+
+            return flask.jsonify(temp_dict)

+ 6 - 2
route/bbs_w.py

@@ -16,9 +16,9 @@ def bbs_w(bbs_num = ''):
         data += '''
             <table id="main_table_set">
                 <tr id="main_table_top_tr">
-                    <td id="main_table_width">''' + load_lang('order') + '''</td>
                     <td id="main_table_width">''' + load_lang('editor') + '''</td>
                     <td id="main_table_width">''' + load_lang('time') + '''</td>
+                    <td id="main_table_width">''' + load_lang('last_comment_time') + '''</td>
                 </tr>
         '''
         
@@ -35,12 +35,16 @@ def bbs_w(bbs_num = ''):
                     curs.execute(db_change('select count(*) from bbs_data where set_name = "comment_date" and (set_id = ? or set_id like ?) order by set_code + 0 desc'), [bbs_num_str + '-' + temp_dict['code'], bbs_num_str + '-' + temp_dict['code'] + '-%'])
                     db_data = curs.fetchall()
                     comment_count = str(db_data[0][0]) if db_data else '0'
+
+                    curs.execute(db_change('select set_data from bbs_data where set_name = "comment_date" and (set_id = ? or set_id like ?) order by set_data desc limit 1'), [bbs_num_str + '-' + temp_dict['code'], bbs_num_str + '-' + temp_dict['code'] + '-%'])
+                    db_data = curs.fetchall()
+                    last_comment_date = db_data[0][0] if db_data else '0'
                     
                     data += '''
                         <tr>
-                            <td>''' + temp_dict['code'] + '''</td>
                             <td>''' + ip_pas(temp_dict['user_id']) + '''</td>
                             <td>''' + temp_dict['date'] + '''</td>
+                            <td>''' + last_comment_date + '''</td>
                         </tr>
                         <tr>
                             <td colspan="3">

+ 25 - 0
route/bbs_w_comment_tool.py

@@ -0,0 +1,25 @@
+from .tool.func import *
+
+def bbs_w_comment_tool(bbs_num = '', post_num = '', comment_num = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        data = ''
+        
+        bbs_num_str = str(bbs_num)
+        post_num_str = str(post_num)
+        
+        data += '''
+            <h2>''' + load_lang('tool') + '''</h2>
+            <ul class="opennamu_ul">
+                <li><a href="/bbs/raw/''' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str) + '/' + url_pas(comment_num) + '">' + load_lang('raw') + '''</a></li>
+                <li><a href="/bbs/edit/''' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str) + '/' + url_pas(comment_num) + '">' + load_lang('edit') + '''</a></li>
+                <!-- <li><a href="/bbs/w/''' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str) + '/blind">' + load_lang('hide') + '''</a></li> -->
+            </ul>
+        '''
+
+        return easy_minify(flask.render_template(skin_check(),
+            imp = [load_lang('bbs_comment_tool'), wiki_set(), wiki_custom(), wiki_css([0, 0])],
+            data = data,
+            menu = [['bbs/w/' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str) + '#' + url_pas(comment_num), load_lang('return')]]
+        ))

+ 63 - 31
route/bbs_w_edit.py

@@ -1,9 +1,10 @@
 from .tool.func import *
 
 from .api_bbs_w_post import api_bbs_w_post
+from .api_bbs_w_comment_one import api_bbs_w_comment_one
 from .edit import edit_editor
 
-def bbs_w_edit(bbs_num = '', post_num = '', do_type = ''):
+def bbs_w_edit(bbs_num = '', post_num = '', comment_num = '', do_type = ''):
     with get_db_connect() as conn:
         curs = conn.cursor()
 
@@ -16,14 +17,20 @@ def bbs_w_edit(bbs_num = '', post_num = '', do_type = ''):
         if not curs.fetchall():
             return redirect('/bbs/main')
         
-        if post_num != '':
-            curs.execute(db_change('select set_data from bbs_data where set_name = "user_id" and set_id = ? and set_code = ?'), [bbs_num, post_num])
-            db_data = curs.fetchall()
-            if not db_data:
-                return redirect('/bbs/main')
+        if comment_num != '':
+            temp_dict = json.loads(api_bbs_w_comment_one(bbs_num_str + '-' + post_num_str + '-' + comment_num).data)
+            if 'comment_user_id' in temp_dict:
+                if not temp_dict['comment_user_id'] == ip and admin_check() != 1:
+                    return re_error('/ban')
             else:
-                if not db_data[0][0] == ip and admin_check() != 1:
+                return redirect('/bbs/main')
+        elif post_num != '':
+            temp_dict = json.loads(api_bbs_w_post(bbs_num_str + '-' + post_num_str).data)
+            if 'user_id' in temp_dict:
+                if not temp_dict['user_id'] == ip and admin_check() != 1:
                     return re_error('/ban')
+            else:
+                return redirect('/bbs/main')
             
         if acl_check(bbs_num_str, 'bbs_edit') == 1:
             return redirect('/bbs/set/' + bbs_num_str)
@@ -52,7 +59,17 @@ def bbs_w_edit(bbs_num = '', post_num = '', do_type = ''):
             
             date = get_time()
 
-            if post_num == '':
+            if comment_num != '':
+                sub_code = (bbs_num_str + '-' + post_num_str + '-' + comment_num).split('-')
+                sub_code_last = ''
+                if len(sub_code) > 2:
+                    sub_code_last = sub_code[len(sub_code) - 1]
+                    del sub_code[len(sub_code) - 1]
+                    
+                sub_code = '-'.join(sub_code)
+
+                curs.execute(db_change("update bbs_data set set_data = ? where set_name = 'comment' and set_code = ? and set_id = ?"), [data, sub_code_last, sub_code])
+            elif post_num == '':
                 curs.execute(db_change("insert into bbs_data (set_name, set_code, set_id, set_data) values ('title', ?, ?, ?)"), [id_data, bbs_num_str, title])
                 curs.execute(db_change("insert into bbs_data (set_name, set_code, set_id, set_data) values ('data', ?, ?, ?)"), [id_data, bbs_num_str, data])
                 curs.execute(db_change("insert into bbs_data (set_name, set_code, set_id, set_data) values ('date', ?, ?, ?)"), [id_data, bbs_num_str, date])
@@ -62,8 +79,13 @@ def bbs_w_edit(bbs_num = '', post_num = '', do_type = ''):
                 curs.execute(db_change("update bbs_data set set_data = ? where set_name = 'data' and set_code = ? and set_id = ?"), [data, id_data, bbs_num_str])
                 curs.execute(db_change("update bbs_data set set_data = ? where set_name = 'date' and set_code = ? and set_id = ?"), [date, id_data, bbs_num_str])
 
-            return redirect('/bbs/w/' + bbs_num_str + '/' + id_data)
+            if comment_num != '':
+                return redirect('/bbs/w/' + bbs_num_str + '/' + id_data + '#' + url_pas(comment_num))
+            else:
+                return redirect('/bbs/w/' + bbs_num_str + '/' + id_data)
         else:
+            option_display = ''
+
             d_list = ['' for _ in range(0, len(i_list))]
             if do_type == 'preview':
                 title = flask.request.form.get('title', '')
@@ -77,15 +99,18 @@ def bbs_w_edit(bbs_num = '', post_num = '', do_type = ''):
                 for for_a in range(0, len(i_list)):
                     d_list[for_a] = flask.request.form.get(i_list[for_a], 'normal')
             else:
-                if post_num == '':
+                if comment_num != '':
+                    temp_dict = json.loads(api_bbs_w_comment_one(bbs_num_str + '-' + post_num_str + '-' + comment_num).data)
+
+                    title = ''
+                    data = temp_dict['comment']
+                    data_preview = ''
+                    option_display = 'display: none;'
+                elif post_num == '':
                     title = ''
                     data = ''
                     data_preview = ''
                 else:
-                    curs.execute(db_change('select set_name, set_data, set_code from bbs_data where set_id = ? and set_code = ?'), [bbs_num, post_num])
-                    db_data = curs.fetchall()
-                    db_data = list(db_data) if db_data else []
-
                     temp_dict = json.loads(api_bbs_w_post(bbs_num_str + '-' + post_num_str).data)
 
                     title = temp_dict['title']
@@ -103,7 +128,10 @@ def bbs_w_edit(bbs_num = '', post_num = '', do_type = ''):
 
                     acl_div[for_a] += '<option value="' + data_list + '" ' + check + '>' + (data_list if data_list != '' else 'normal') + '</option>'
             
-            if post_num == '':
+            if comment_num != '':
+                form_action = 'formaction="/bbs/edit/' + bbs_num_str + '/' + post_num_str + '/' + url_pas(comment_num) + '"'
+                form_action_preview = 'formaction="/bbs/edit/preview/' + bbs_num_str + '/' + post_num_str + '/' + url_pas(comment_num) + '"'
+            elif post_num == '':
                 form_action = 'formaction="/bbs/edit/' + bbs_num_str + '"'
                 form_action_preview = 'formaction="/bbs/edit/preview/' + bbs_num_str + '"'
             else:
@@ -115,7 +143,9 @@ def bbs_w_edit(bbs_num = '', post_num = '', do_type = ''):
             if editor_top_text != '':
                 editor_top_text += '<hr class="main_hr">'
 
-            if post_num == '':
+            if comment_num != '':
+                bbs_title = load_lang('bbs_comment_edit')
+            elif post_num == '':
                 bbs_title = load_lang('post_add')
             else:
                 bbs_title = load_lang('post_edit')
@@ -124,8 +154,8 @@ def bbs_w_edit(bbs_num = '', post_num = '', do_type = ''):
                 imp = [bbs_title, wiki_set(), wiki_custom(), wiki_css([0, 0])],
                 data =  editor_top_text + '''
                     <form method="post">                        
-                        <input placeholder="''' + load_lang('title') + '''" name="title" value="''' + html.escape(title) + '''">
-                        <hr class="main_hr">
+                        <input style="''' + option_display + '''" placeholder="''' + load_lang('title') + '''" name="title" value="''' + html.escape(title) + '''">
+                        <hr style="''' + option_display + '''" class="main_hr">
 
                         ''' + edit_editor(curs, ip, data, 'bbs') + '''
                         <hr class="main_hr">
@@ -138,19 +168,21 @@ def bbs_w_edit(bbs_num = '', post_num = '', do_type = ''):
                         <hr class="main_hr">
                         <div id="opennamu_preview_area">''' + data_preview + '''</div>
 
-                        ''' + render_simple_set('''
-                            <hr class="main_hr">
-                            <a href="/acl/TEST#exp">(''' + load_lang('reference') + ''')</a>
-                            <h2>''' + load_lang('acl') + '''</h2>
-                            <h3>''' + load_lang('post_view_acl') + '''</h3>
-                            <select name="post_view_acl">''' + acl_div[0] + '''</select>
-
-                            <h4>''' + load_lang('post_comment_acl') + '''</h4>
-                            <select name="post_comment_acl">''' + acl_div[1] + '''</select>
-
-                            <h2>''' + load_lang('markup') + '''</h2>
-                            ''' + load_lang('not_working') + '''
-                        ''') + '''
+                        <div style="''' + option_display + '''">
+                            ''' + render_simple_set('''
+                                <hr class="main_hr">
+                                <a href="/acl/TEST#exp">(''' + load_lang('reference') + ''')</a>
+                                <h2>''' + load_lang('acl') + '''</h2>
+                                <h3>''' + load_lang('post_view_acl') + '''</h3>
+                                <select name="post_view_acl">''' + acl_div[0] + '''</select>
+
+                                <h4>''' + load_lang('post_comment_acl') + '''</h4>
+                                <select name="post_comment_acl">''' + acl_div[1] + '''</select>
+
+                                <h2>''' + load_lang('markup') + '''</h2>
+                                ''' + load_lang('not_working') + '''
+                            ''') + '''
+                        </div>
                     </form>
                 ''',
                 menu = [['bbs/w/' + bbs_num_str, load_lang('return')]]

+ 5 - 7
route/bbs_w_post.py

@@ -28,7 +28,7 @@ def bbs_w_post_comment(user_id, sub_code, comment_num, bbs_num_str, post_num_str
 
         date = ''
         date += '<a href="javascript:opennamu_change_comment(\'' + sub_code_check + '\');">(' + load_lang('comment') + ')</a> '
-        date += '<a href="/bbs/w/' + bbs_num_str + '/' + post_num_str + '/comment/' + sub_code_check + '/tool">(' + load_lang('tool') + ')</a> '
+        date += '<a href="/bbs/tool/' + bbs_num_str + '/' + post_num_str + '/' + sub_code_check + '">(' + load_lang('tool') + ')</a> '
         date += temp_dict['comment_date']
 
         comment_data += '<span style="padding-left: 20px;"></span>' * margin_count
@@ -135,7 +135,6 @@ def bbs_w_post(bbs_num = '', post_num = '', do_type = ''):
                     )
 
                 date = ''
-                date += '<a href="/bbs/w/' + bbs_num_str + '/' + post_num_str + '/tool">(' + load_lang('tool') + ')</a> '
                 date += temp_dict['date']
 
                 data = ''
@@ -165,7 +164,7 @@ def bbs_w_post(bbs_num = '', post_num = '', do_type = ''):
                         color = 'default'
                         
                     date = ''
-                    date += '<a href="/bbs/w/' + bbs_num_str + '/' + post_num_str + '/comment/' + str(count) + '/tool">(' + load_lang('tool') + ')</a> '
+                    date += '<a href="/bbs/tool/' + bbs_num_str + '/' + post_num_str + '/' + str(count) + '">(' + load_lang('tool') + ')</a> '
                     date += temp_dict['comment_date']
 
                     data += api_topic_thread_make(
@@ -204,7 +203,7 @@ def bbs_w_post(bbs_num = '', post_num = '', do_type = ''):
                 return easy_minify(flask.render_template(skin_check(),
                     imp = [bbs_name, wiki_set(), wiki_custom(), wiki_css(['(' + load_lang('bbs') + ')', 0])],
                     data = data,
-                    menu = [['bbs/w/' + bbs_num_str, load_lang('return')], ['bbs/edit/' + bbs_num_str + '/' + post_num_str, load_lang('edit')]]
+                    menu = [['bbs/w/' + bbs_num_str, load_lang('return')], ['bbs/edit/' + bbs_num_str + '/' + post_num_str, load_lang('edit')], ['bbs/tool/' + bbs_num_str + '/' + post_num_str, load_lang('tool')]]
                 ))
         else:
             # db_data_2[0][0] == 'comment'
@@ -293,7 +292,6 @@ def bbs_w_post(bbs_num = '', post_num = '', do_type = ''):
 
                 date = ''
                 date += '<a href="javascript:opennamu_change_comment(\'0\');">(' + load_lang('comment') + ')</a> '
-                date += '<a href="/bbs/w/' + bbs_num_str + '/' + post_num_str + '/tool">(' + load_lang('tool') + ')</a> '
                 date += temp_dict['date']
 
                 data = ''
@@ -340,7 +338,7 @@ def bbs_w_post(bbs_num = '', post_num = '', do_type = ''):
                 bbs_comment_form = ''
                 if bbs_comment_acl == 0:
                     bbs_comment_form += '''
-                        ''' + comment_select + ''' <a href="javascript:opennamu_return_comment();">(R)</a>
+                        ''' + comment_select + ''' <a href="javascript:opennamu_return_comment();">(''' + load_lang('return') + ''')</a>
                         <hr class="main_hr">
                         
                         ''' + edit_editor(curs, ip, text, 'bbs_comment') + '''
@@ -364,5 +362,5 @@ def bbs_w_post(bbs_num = '', post_num = '', do_type = ''):
                 return easy_minify(flask.render_template(skin_check(),
                     imp = [bbs_name, wiki_set(), wiki_custom(), wiki_css(['(' + load_lang('bbs') + ')', 0])],
                     data = data,
-                    menu = [['bbs/w/' + bbs_num_str, load_lang('return')], ['bbs/edit/' + bbs_num_str + '/' + post_num_str, load_lang('edit')]]
+                    menu = [['bbs/w/' + bbs_num_str, load_lang('return')], ['bbs/edit/' + bbs_num_str + '/' + post_num_str, load_lang('edit')], ['bbs/tool/' + bbs_num_str + '/' + post_num_str, load_lang('tool')]]
                 ))

+ 24 - 0
route/bbs_w_tool.py

@@ -0,0 +1,24 @@
+from .tool.func import *
+
+def bbs_w_tool(bbs_num = '', post_num = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        data = ''
+        
+        bbs_num_str = str(bbs_num)
+        post_num_str = str(post_num)
+        
+        data += '''
+            <h2>''' + load_lang('tool') + '''</h2>
+            <ul class="opennamu_ul">
+                <li><a href="/bbs/raw/''' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str) + '">' + load_lang('raw') + '''</a></li>
+                <!-- <li><a href="/bbs/blind/''' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str) + '">' + load_lang('hide') + '''</a></li> -->
+            </ul>
+        '''
+
+        return easy_minify(flask.render_template(skin_check(),
+            imp = [load_lang('bbs_post_tool'), wiki_set(), wiki_custom(), wiki_css([0, 0])],
+            data = data,
+            menu = [['bbs/w/' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str), load_lang('return')]]
+        ))

+ 2 - 4
route/edit.py

@@ -53,10 +53,8 @@ def edit_editor(curs, ip, data_main = '', do_type = 'edit'):
     if monaco_on == 'use':
         editor_display = 'style="display: none;"'
         add_get_file = '''
-            <link   rel="stylesheet"
-                    data-name="vs/editor/editor.main" 
-                    href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.37.1/min/vs/editor/editor.main.min.css">
-            <script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.37.1/min/vs/loader.min.js"></script>
+            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.41.0/min/vs/editor/editor.main.min.css" integrity="sha512-MFDhxgOYIqLdcYTXw7en/n5BshKoduTitYmX8TkQ+iJOGjrWusRi8+KmfZOrgaDrCjZSotH2d1U1e/Z1KT6nWw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
+            <script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.41.0/min/vs/loader.min.js" integrity="sha512-A+6SvPGkIN9Rf0mUXmW4xh7rDvALXf/f0VtOUiHlDUSPknu2kcfz1KzLpOJyL2pO+nZS13hhIjLqVgiQExLJrw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
         '''
 
         monaco_editor_top = '<a href="javascript:opennamu_edit_turn_off_monaco();">(' + load_lang('turn_off_monaco') + ')</a>'

+ 1 - 1
route/edit_delete.py

@@ -49,7 +49,7 @@ def edit_delete(name):
 
                 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("insert into back (title, link, type, data) 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])

+ 4 - 0
route/give_user_fix.py

@@ -53,6 +53,10 @@ def give_user_fix(user_name = ''):
                 imp = [load_lang('user_fix'), wiki_set(), wiki_custom(), wiki_css([0, 0])],
                 data = '''
                     <form method="post">
+                        <div id="opennamu_get_user_info">''' + html.escape(user_name) + '''</div>
+                        <hr class="main_hr">
+                        <a href="/change/user_name/''' + url_pas(user_name) + '''">(''' + load_lang('change_user_name') + ''')</a>
+                        <hr class="main_hr">
                         <select name="select">
                             <option value="password_change">''' + load_lang('password_change') + '''</option>
                             <option value="2fa_password_change">''' + load_lang('2fa_password_change') + '''</option>

+ 1 - 14
route/login_register.py

@@ -44,22 +44,9 @@ def login_register_2():
                 if password_min_length > len(user_pw):
                     return re_error('/error/40')
 
-            # ID 글자 확인
-            if re.search(r'(?:[^A-Za-zㄱ-힣0-9])', user_id):
+            if do_user_name_check(user_id) == 1:
                 return re_error('/error/8')
 
-            # ID 필터
-            curs.execute(db_change('select html from html_filter where kind = "name"'))
-            set_d = curs.fetchall()
-            for i in set_d:
-                check_r = re.compile(i[0], re.I)
-                if check_r.search(user_id):
-                    return re_error('/error/8')
-
-            # ID 길이 제한 (32글자)
-            if len(user_id) > 32:
-                return re_error('/error/7')
-
             # 중복 확인
             curs.execute(db_change("select id from user_set where id = ?"), [user_id])
             if curs.fetchall():

+ 2 - 1
route/main_setting_external.py

@@ -59,7 +59,8 @@ def main_setting_external():
             re_ver_list = {
                 '' : 'reCAPTCHA v2',
                 'v3' : 'reCAPTCHA v3',
-                'h' : 'hCAPTCHA'
+                'h' : 'hCAPTCHA',
+                'cf' : 'Turnstile'
             }
             re_ver = ''
             for i in re_ver_list:

+ 2 - 2
route/main_setting_main.py

@@ -255,12 +255,12 @@ def main_setting_main(db_set):
                         <input type="checkbox" name="move_with_redirect" ''' + check_box_div[13] + '''> ''' + load_lang('move_with_redirect') + ''' (''' + load_lang('not_working') + ''')
                         <hr class="main_hr">
 
-                        <span>''' + load_lang('slow_thread') + ''' (''' + load_lang('second') + ''') (''' + load_lang('off') + ''' : ''' + load_lang('empty') + ''') (''' + load_lang('not_working') + ''')</span>
+                        <span>''' + load_lang('slow_thread') + ''' (''' + load_lang('second') + ''') (''' + load_lang('off') + ''' : ''' + load_lang('empty') + ''')</span>
                         <hr class="main_hr">
                         <input name="slow_thread" value="''' + html.escape(d_list[38]) + '''">
                         <hr class="main_hr">
 
-                        <span>''' + load_lang('edit_timeout') + ''' (''' + load_lang('second') + ''') (''' + load_lang('off') + ''' : ''' + load_lang('empty') + ''')</span>
+                        <span>''' + load_lang('edit_timeout') + ''' (''' + load_lang('second') + ''') (''' + load_lang('off') + ''' : ''' + load_lang('empty') + ''') (''' + load_lang('linux_only') + ''')</span>
                         <hr class="main_hr">
                         <input name="edit_timeout" value="''' + html.escape(d_list[39]) + '''">
                         <hr class="main_hr">

+ 5 - 0
route/main_setting_skin_set.py

@@ -60,9 +60,14 @@ def main_setting_skin_set():
                             ''' + set_data["main_css_bold"] + '''
                         </select>
                         <h3>''' + load_lang("category") + '''</h3>
+                        <h4>''' + load_lang("position") + '''</h4>
                         <select name="main_css_category_set">
                             ''' + set_data["main_css_category_set"] + '''
                         </select>
+                        <h4>''' + load_lang("category_change_title") + '''</h4>
+                        <select name="main_css_category_change_title">
+                            ''' + set_data["main_css_category_change_title"] + '''
+                        </select>
                         <h3>''' + load_lang("footnote") + ''' (''' + load_lang('beta') + ''')</h3>
                         <h4>''' + load_lang("footnote_render") + '''</h4>
                         <select name="main_css_footnote_set">

+ 16 - 3
route/main_sys_restart.py

@@ -11,14 +11,27 @@ def main_sys_restart():
             print('----')
             print('Restart')
 
-            run_list = [sys.executable, 'python' + python_ver, 'python3', 'python']
+            python_ver = ''
+            python_ver = str(sys.version_info.major) + '.' + str(sys.version_info.minor)
+
+            run_list = [sys.executable, 'python' + python_ver, 'python3', 'python', 'py -' + python_ver]
             for exe_name in run_list:
+                try:
+                    os.execl(exe_name, sys.executable, *sys.argv)
+                except:
+                    pass
+
+                try:
+                    os.execl(exe_name, '"' + sys.executable + '"', *sys.argv)
+                except:
+                    pass
+
                 try:
                     os.execl(exe_name, os.path.abspath(__file__), *sys.argv)
                 except:
                     pass
-            
-            return re_error('/error/33')
+            else:
+                return re_error('/error/33')
         else:
             return easy_minify(flask.render_template(skin_check(),
                 imp = [load_lang('wiki_restart'), wiki_set(), wiki_custom(), wiki_css([0, 0])],

+ 3 - 4
route/recent_change.py

@@ -7,7 +7,7 @@ def recent_change_send_render(data):
 
         return '<a href="/w/' + url_pas(data_unescape) + '">' + match + '</a>'
 
-    if data == '&lt;br&gt;' or data == '':
+    if data == '&lt;br&gt;' or data == '' or re.search(r'^ +$', data):
         data = '<br>'
     else:
         data = data.replace('javascript:', '')
@@ -30,7 +30,6 @@ def recent_change(name = None, tool = ''):
             ban = ''
             select = ''
             sub = ''
-            admin_6 = admin_check(6)
             admin = admin_check()
             div = '''
                 <table id="main_table_set">
@@ -93,9 +92,9 @@ def recent_change(name = None, tool = ''):
                 set_type = '' if set_type == 'edit' else set_type
 
                 data_list = []
-                curs.execute(db_change('select id, title from rc where type = ? order by date desc'), [set_type])
+                curs.execute(db_change('select title, id from rc where type = ? order by date desc'), [set_type])
                 for i in curs.fetchall():
-                    curs.execute(db_change('select id, title, date, ip, send, leng, hide from history where id = ? and title = ?'), i)
+                    curs.execute(db_change('select id, title, date, ip, send, leng, hide from history where title = ? and id = ?'), i)
                     data_list += curs.fetchall()
 
             div += '</tr>'

+ 93 - 48
route/tool/func.py

@@ -35,13 +35,28 @@ if data_up_date == 1:
         f.write(version_list['beta']['r_ver'])
     
     if platform.system() in ('Linux', 'Windows'):
+        python_ver = ''
         python_ver = str(sys.version_info.major) + '.' + str(sys.version_info.minor)
 
-        run_list = [sys.executable, 'python' + python_ver, 'python3', 'python']
+        run_list = [sys.executable, 'python' + python_ver, 'python3', 'python', 'py -' + python_ver]
         for exe_name in run_list:
             try:
                 subprocess.check_call([exe_name, "-m", "pip", "install", "--upgrade", "--user", "-r", "requirements.txt"])
-                os.execl(exe_name, os.path.abspath(__file__), *sys.argv)
+
+                try:
+                    os.execl(exe_name, sys.executable, *sys.argv)
+                except:
+                    pass
+
+                try:
+                    os.execl(exe_name, '"' + sys.executable + '"', *sys.argv)
+                except:
+                    pass
+
+                try:
+                    os.execl(exe_name, os.path.abspath(__file__), *sys.argv)
+                except:
+                    pass
             except:
                 pass
         else:
@@ -81,8 +96,6 @@ global_wiki_set = {}
 
 global_db_set = ''
 
-conn = ''
-
 # Func
 # Func-main
 def do_db_set(db_set):
@@ -124,18 +137,9 @@ def get_init_set_list(need = 'all'):
         return init_set_list[need]
 
 class get_db_connect:
-    # 임시 DB 커넥션 동작 구조
-    # Init 파트
-    # DB 커넥트(get_db_connect_old) -> func.py로 conn 넘겨줌
-    # route 파트
-    # DB 새로 커넥트 -> func.py에서 쓰던 conn은 conn_sub로 보관 ->
-    # func.py로 conn 넘겨줌 -> 모든 라우터 과정이 끝나면 conn_sub를 다시 func.py에 conn으로 넘겨줌 ->
-    # DB 커넥트 종료
     def __init__(self):
         global global_db_set
-        global conn
-        
-        self.conn_sub = conn
+
         self.db_set = global_db_set
         
     def __enter__(self):
@@ -306,7 +310,7 @@ def get_db_table_list():
     create_data['acl'] = ['title', 'data', 'type']
 
     # 개편 예정 (data_link로 변경)
-    create_data['back'] = ['title', 'link', 'type']
+    create_data['back'] = ['title', 'link', 'type', 'data']
 
     # 폐지 예정 (topic_set으로 통합) [가장 시급]
     create_data['topic_set'] = ['thread_code', 'set_name', 'set_id', 'set_data']
@@ -607,9 +611,7 @@ def update(ver_num, set_data):
 
         if ver_num < 3500355:
             # other coverage 오류 해결
-            curs.execute(db_change(
-                "update other set coverage = '' where coverage is null"
-            ))
+            curs.execute(db_change("update other set coverage = '' where coverage is null"))
 
         if ver_num < 3500358:
             curs.execute(db_change("drop index history_index"))
@@ -630,9 +632,7 @@ def update(ver_num, set_data):
                 if db_data_2:
                     curs.execute(db_change("insert into data_set (doc_name, doc_rev, set_name, set_data) values (?, '', 'last_edit', ?)"), [for_a[0], db_data_2[0][0]])
 
-            curs.execute(db_change(
-                'delete from acl where title like "file:%" and data = "admin" and type like "decu%"'
-            ))
+            curs.execute(db_change('delete from acl where title like "file:%" and data = "admin" and type like "decu%"'))
 
             print("Update 3500360 complete")
 
@@ -645,8 +645,14 @@ def update(ver_num, set_data):
                         'delete from user_set where id = ? and name = "email"'
                     ), [db_data[0]])
 
-    #    if ver_num < 3500361:
+        # create_data['history'] = ['id', 'title', 'data', 'date', 'ip', 'send', 'leng', 'hide', 'type']
+        # create_data['rc'] = ['id', 'title', 'date', 'type']
+        if ver_num == 3500362:
+            curs.execute(db_change("drop index history_index"))
+            curs.execute(db_change("create index history_index on history (title, ip)"))
 
+        if ver_num < 3500365:
+            curs.execute(db_change("update back set data = '' where data is null"))
 
         conn.commit()
 
@@ -1082,7 +1088,7 @@ def wiki_css(data):
     data += ['' for _ in range(0, 3 - len(data))]
     
     data_css = ''
-    data_css_ver = '179'
+    data_css_ver = '180'
     
     # Func JS + Defer
     data_css += '<script src="/views/main_css/js/func/func.js?ver=' + data_css_ver + '"></script>'
@@ -1580,7 +1586,14 @@ def captcha_get():
                             '});' + \
                         '</script>' + \
                     ''
+                elif rec_ver[0][0] == 'cf':
+                    data += '' + \
+                        '<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha" async defer></script>' + \
+                        '<div class="g-recaptcha" data-sitekey="' + recaptcha[0][0] + '"></div>' + \
+                        '<hr class="main_hr">' + \
+                    ''
                 else:
+                    # rec_ver[0][0] == 'h'
                     data += '''
                         <script src="https://js.hcaptcha.com/1/api.js" async defer></script>
                         <div class="h-captcha" data-sitekey="''' + recaptcha[0][0] + '''"></div>
@@ -1601,27 +1614,61 @@ def captcha_post(re_data, num = 1):
             rec_ver = curs.fetchall()
             if captcha_get() != '':
                 if not rec_ver or rec_ver[0][0] in ('', 'v3'):
-                    data = requests.get(
-                        'https://www.google.com/recaptcha/api/siteverify' + \
-                        '?secret=' + sec_re[0][0] + '&response=' + re_data
+                    data = requests.post(
+                        'https://www.google.com/recaptcha/api/siteverify',
+                        data = {
+                            "secret" : sec_re[0][0],
+                            "response" : re_data
+                        }
+                    )
+                elif rec_ver[0][0] == 'cf':
+                    data = requests.post(
+                        'https://challenges.cloudflare.com/turnstile/v0/siteverify',
+                        data = {
+                            "secret" : sec_re[0][0],
+                            "response" : re_data
+                        }
                     )
-                    if data.status_code == 200:
-                        json_data = json.loads(data.text)
-                        if json_data['success'] != True:
-                            return 1
                 else:
-                    data = requests.get(
-                        'https://hcaptcha.com/siteverify' + \
-                        '?secret=' + sec_re[0][0] + '&response=' + re_data
+                    # rec_ver[0][0] == 'h'
+                    data = requests.post(
+                        'https://hcaptcha.com/siteverify',
+                        data = {
+                            "secret" : sec_re[0][0],
+                            "response" : re_data
+                        }
                     )
-                    if data.status_code == 200:
-                        json_data = json.loads(data.text)
-                        if json_data['success'] != True:
-                            return 1
+                    
+                if data.status_code == 200:
+                    json_data = json.loads(data.text)
+                    if json_data['success'] != True:
+                        return 1
 
         return 0
 
 # Func-user
+def do_user_name_check(user_name):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        # ID 글자 확인
+        if re.search(r'(?:[^A-Za-zㄱ-힣0-9])', user_name):
+            return 1
+
+        # ID 필터
+        curs.execute(db_change('select html from html_filter where kind = "name"'))
+        set_d = curs.fetchall()
+        for i in set_d:
+            check_r = re.compile(i[0], re.I)
+            if check_r.search(user_name):
+                return 1
+
+        # ID 길이 제한 (32글자)
+        if len(user_name) > 32:
+            return 1
+        
+        return 0
+
 def get_admin_auth_list(num = None):
     # without_DB
 
@@ -1914,7 +1961,7 @@ def acl_check(name = 'test', tool = '', topic_num = '1'):
                 else:
                     acl_data = [['normal']]
 
-            except_ban_tool_list = ['render', 'topic_view']
+            except_ban_tool_list = ['render', 'topic_view', 'bbs_view']
             if acl_data[0][0] != 'normal':
                 if not acl_data[0][0] in ['ban', 'ban_admin'] and not tool in except_ban_tool_list:
                     if get_ban == 1:
@@ -2125,11 +2172,13 @@ def ip_pas(raw_ip, type_data = 0):
 
                     change_ip = 1
                 else:
-                    ip = raw_ip
+                    curs.execute(db_change('select data from user_set where name = "user_name" and id = ?'), [raw_ip])
+                    db_data = curs.fetchall()
+                    ip = db_data[0][0] if db_data else raw_ip
                 
             if type_data == 0 and change_ip == 0:
                 if is_this_ip == 0:
-                    ip = '<a href="/w/' + url_pas('user:' + raw_ip) + '">' + raw_ip + '</a>'
+                    ip = '<a href="/w/' + url_pas('user:' + raw_ip) + '">' + ip + '</a>'
                     
                     if admin_check('all', None, raw_ip) == 1:
                         ip = '<b>' + ip + '</b>'
@@ -2593,9 +2642,10 @@ def re_error(data):
             elif num == 6:
                 data = load_lang('same_id_exist_error')
             elif num == 7:
+                # 폐지
                 data = load_lang('long_id_error')
             elif num == 8:
-                data = load_lang('id_char_error') + ' <a href="/name_filter">(' + load_lang('id_filter_list') + ')</a>'
+                data = load_lang('long_id_error') + '<br>' + load_lang('id_char_error') + ' <a href="/name_filter">(' + load_lang('id_filter_list') + ')</a>'
             elif num == 9:
                 data = load_lang('file_exist_error')
             elif num == 10:
@@ -2615,8 +2665,7 @@ def re_error(data):
             elif num == 17:
                 curs.execute(db_change('select data from other where name = "upload"'))
                 db_data = curs.fetchall()
-                file_max = int(number_check(db_data[0][0])) if db_data and db_data[0][0] != '' else '2'
-
+                file_max = number_check(db_data[0][0]) if db_data and db_data[0][0] != '' else '2'
                 data = load_lang('file_capacity_error') + file_max
             elif num == 18:
                 data = load_lang('email_send_error')
@@ -2670,11 +2719,7 @@ def re_error(data):
             elif num == 40:
                 curs.execute(db_change("select data from other where name = 'password_min_length'"))
                 db_data = curs.fetchall()
-                if db_data and db_data[0][0] != '':
-                    password_min_length = db_data[0][0]
-                else:
-                    password_min_length = ''
-                    
+                password_min_length = '' if not db_data else db_data[0][0]
                 data = load_lang('error_password_length_too_short') + password_min_length
             elif num == 41:
                 curs.execute(db_change("select data from other where name = 'edit_timeout'"))

+ 1 - 1
route/tool/func_render.py

@@ -91,7 +91,7 @@ class class_do_render:
                 backlink = []
 
             if backlink != []:
-                curs.executemany(db_change("insert into back (link, title, type) values (?, ?, ?)"), data_end[2]['backlink'])
+                curs.executemany(db_change("insert into back (link, title, type, data) values (?, ?, ?, ?)"), data_end[2]['backlink'])
                 curs.execute(db_change("delete from back where title = ? and type = 'no'"), [doc_name])
 
             self.conn.commit()

+ 20 - 18
route/tool/func_render_namumark.py

@@ -796,11 +796,11 @@ class class_do_render_namumark:
                         db_data = self.curs.fetchall()
                         if db_data:
                             link_exist = ''
-                            self.data_backlink += [[self.doc_name, 'file:' + link_main, 'file']]
+                            self.data_backlink += [[self.doc_name, 'file:' + link_main, 'file', '']]
                         else:
                             link_exist = 'opennamu_not_exist_link'
-                            self.data_backlink += [[self.doc_name, 'file:' + link_main, 'no']]
-                            self.data_backlink += [[self.doc_name, 'file:' + link_main, 'file']]
+                            self.data_backlink += [[self.doc_name, 'file:' + link_main, 'no', '']]
+                            self.data_backlink += [[self.doc_name, 'file:' + link_main, 'file', '']]
                         
                         link_extension_regex = r'\.([^.]+)$'
                         link_extension = re.search(link_extension_regex, link_main)
@@ -884,9 +884,6 @@ class class_do_render_namumark:
                 elif re.search(r'^(분류|category):', link_main, flags = re.I):
                     link_main = re.sub(r'^(분류|category):', '', link_main, flags = re.I)
 
-                    if link_data[1]:
-                        link_main += link_data[1]
-
                     category_blur = ''
                     if re.search(r'#blur$', link_main, flags = re.I):
                         link_main = re.sub(r'#blur$', '', link_main, flags = re.I)
@@ -894,6 +891,9 @@ class class_do_render_namumark:
                         category_blur = 'opennamu_category_blur'
                     
                     link_sub = link_main
+                    link_view = ''
+                    if len(link_data) > 1 and link_data[1]:
+                        link_view = link_data[1]
 
                     link_main = self.get_tool_data_restore(link_main, do_type = 'slash')
                     link_main = html.unescape(link_main)
@@ -905,11 +905,12 @@ class class_do_render_namumark:
                         db_data = self.curs.fetchall()
                         if db_data:
                             link_exist = ''
-                            self.data_backlink += [[self.doc_name, 'category:' + link_main, 'cat']]
                         else:
                             link_exist = 'opennamu_not_exist_link'
-                            self.data_backlink += [[self.doc_name, 'category:' + link_main, 'no']]
-                            self.data_backlink += [[self.doc_name, 'category:' + link_main, 'cat']]
+                            self.data_backlink += [[self.doc_name, 'category:' + link_main, 'no', '']]
+
+                        self.data_backlink += [[self.doc_name, 'category:' + link_main, 'cat', '']]
+                        self.data_backlink += [[self.doc_name, 'category:' + link_main, 'cat_view', link_view]]
 
                         link_main = url_pas(link_main)
 
@@ -1033,15 +1034,15 @@ class class_do_render_namumark:
                         self.curs.execute(db_change("select title from data where title = ?" + self.link_case_insensitive), [link_main])
                         db_data = self.curs.fetchall()
                         if not db_data:
-                            self.data_backlink += [[self.doc_name, link_main, 'no']]
-                            self.data_backlink += [[self.doc_name, link_main, '']]
+                            self.data_backlink += [[self.doc_name, link_main, 'no', '']]
+                            self.data_backlink += [[self.doc_name, link_main, '', '']]
                             link_exist = 'opennamu_not_exist_link'
                         else:
                             link_main = db_data[0][0]
-                            self.data_backlink += [[self.doc_name, link_main, '']]
+                            self.data_backlink += [[self.doc_name, link_main, '', '']]
 
                     link_same = ''
-                    if link_main == self.doc_name and self.doc_include == '':
+                    if link_main == self.doc_name:
                         link_same = 'opennamu_same_link'
 
                     link_main = url_pas(link_main)
@@ -1174,7 +1175,7 @@ class class_do_render_namumark:
                     self.curs.execute(db_change("select data from data where title = ?"), [include_name])
                     db_data = self.curs.fetchall()
                     if db_data:
-                        self.data_backlink += [[self.doc_name, include_name, 'include']]
+                        self.data_backlink += [[self.doc_name, include_name, 'include', '']]
                         include_data = db_data[0][0].replace('\r', '')
 
                         # include link func
@@ -1189,14 +1190,14 @@ class class_do_render_namumark:
                         # remove end br
                         include_data = re.sub('^\n+', '', include_data)
 
-                        self.data_include += [[self.doc_include + 'opennamu_include_' + str(include_num), include_name, include_data, 'style="display: inline;"']]
+                        self.data_include += [[self.doc_include + 'opennamu_include_' + str(include_num), self.doc_name, include_data, 'style="display: inline;"']]
 
                         data_name = self.get_tool_data_storage('' + \
                             include_link + \
                             '<div id="' + self.doc_include + 'opennamu_include_' + str(include_num) + '"></div>' + \
                         '', '', match_org)
                     else:
-                        self.data_backlink += [[self.doc_name, include_name, 'no']]
+                        self.data_backlink += [[self.doc_name, include_name, 'no', '']]
 
                         include_link = '<div><a class="opennamu_not_exist_link" href="/w/' + url_pas(include_name) + '">(' + include_name_org + ')</a></div>'
 
@@ -1354,7 +1355,7 @@ class class_do_render_namumark:
             link_main = self.get_tool_data_restore(link_main, do_type = 'slash')
             link_main = html.unescape(link_main)
 
-            self.data_backlink += [[self.doc_name, link_main, 'redirect']]
+            self.data_backlink += [[self.doc_name, link_main, 'redirect', '']]
 
             link_main = url_pas(link_main)
 
@@ -2200,8 +2201,9 @@ class class_do_render_namumark:
             self.do_render_link()
             self.do_render_text()
             self.do_render_hr()
-            self.do_render_heading()
+
             self.do_redner_footnote()
+            self.do_render_heading()
             
         self.do_render_last()
 

+ 8 - 0
route/user_setting.py

@@ -95,6 +95,10 @@ def user_setting():
                 fa_data_pw = curs.fetchall()
                 fa_data_pw = load_lang('2fa_password_change') if fa_data_pw else load_lang('2fa_password')
 
+                curs.execute(db_change('select data from user_set where name = "user_name" and id = ?'), [ip])
+                db_data = curs.fetchall()
+                user_name = db_data[0][0] if db_data else ip
+
                 curs.execute(db_change('select data from user_set where name = "sub_user_name" and id = ?'), [ip])
                 db_data = curs.fetchall()
                 sub_user_name = db_data[0][0] if db_data else ''
@@ -130,6 +134,10 @@ def user_setting():
                             <select name="2fa" id="twofa_check_input">''' + fa_data_select + '''</select>
                             <hr class="main_hr">
                             <input type="password" name="2fa_pw" placeholder="''' + fa_data_pw + '''">
+                            <h2>''' + load_lang('main_user_name') + '''</h2>
+                            <a href="/change/user_name">(''' + load_lang('change_user_name') + ''')</a>
+                            <hr class="main_hr">
+                            ''' + load_lang('user_name') + ''' : ''' + html.escape(user_name) + '''
                             <h2>''' + load_lang('sub_user_name') + '''</h2>
                             <input name="sub_user_name" value="''' + html.escape(sub_user_name) + '''" placeholder="''' + load_lang('sub_user_name') + '''">
                             <hr class="main_hr">

+ 1 - 1
route/user_setting_head.py

@@ -73,5 +73,5 @@ def user_setting_head(skin_name = ''):
                         <button id="opennamu_save_button" type="submit">''' + load_lang('save') + '''</button>
                     </form>
                 ''',
-                menu = [['user', load_lang('return')]]
+                menu = [['change', load_lang('return')]]
             ))

+ 11 - 1
route/user_setting_skin_set_main.py

@@ -68,6 +68,10 @@ def user_setting_skin_set_main_set_list():
             ['default', load_lang('default')],
             ['off', load_lang('off')],
             ['on', load_lang('use')]
+        ], 'main_css_category_change_title' : [
+            ['default', load_lang('default')],
+            ['off', load_lang('off')],
+            ['on', load_lang('use')]
         ]
     }
 
@@ -152,10 +156,16 @@ def user_setting_skin_set_main():
                             ''' + set_data["main_css_bold"] + '''
                         </select>
                         <h3>''' + load_lang("category") + '''</h3>
+                        <h4>''' + load_lang("position") + '''</h4>
                         ''' + set_data_main["main_css_category_set"] + '''
                         <select name="main_css_category_set">
                             ''' + set_data["main_css_category_set"] + '''
                         </select>
+                        <h4>''' + load_lang("category_change_title") + '''</h4>
+                        ''' + set_data_main["main_css_category_change_title"] + '''
+                        <select name="main_css_category_change_title">
+                            ''' + set_data["main_css_category_change_title"] + '''
+                        </select>
                         <h3>''' + load_lang("footnote") + ''' (''' + load_lang('beta') + ''')</h3>
                         <h4>''' + load_lang("footnote_render") + '''</h4>
                         ''' + set_data_main["main_css_footnote_set"] + '''
@@ -224,5 +234,5 @@ def user_setting_skin_set_main():
                         <button type="submit">''' + load_lang('save') + '''</button>
                     </form>
                 '''),
-                menu = [['change', load_lang('user_setting')], ['change/skin_set', load_lang('skin_set')]]
+                menu = [['change', load_lang('user_setting')], ['change/skin_set', load_lang('skin_set')], ['setting/skin_set', load_lang('main_skin_set_default')]]
             ))

+ 54 - 0
route/user_setting_user_name.py

@@ -0,0 +1,54 @@
+from .tool.func import *
+
+def user_setting_user_name(user_name = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        ip = ip_check()
+        if user_name != '':
+            if admin_check() != 1:
+                return re_error('/error/3')
+            else:
+                ip = user_name
+    
+        if ip_or_user(ip) == 0:
+            if flask.request.method == 'POST':
+                auto_data = ['user_name', flask.request.form.get('new_user_name', '')]
+                if do_user_name_check(auto_data[1]) == 1:
+                    return re_error('/error/8')
+
+                curs.execute(db_change('select data from user_set where name = ? and data = ?'), [auto_data[0], auto_data[1]])
+                if curs.fetchall():
+                    return re_error('/error/6')
+                else:
+                    curs.execute(db_change('select data from user_set where name = ? and id = ?'), [auto_data[0], ip])
+                    if curs.fetchall():
+                        curs.execute(db_change("update user_set set data = ? where name = ? and id = ?"), [auto_data[1], auto_data[0], ip])
+                    else:
+                        curs.execute(db_change("insert into user_set (name, id, data) values (?, ?, ?)"), [auto_data[0], ip, auto_data[1]])
+
+                    if user_name != '':
+                        return redirect('/change/user_name/' + url_pas(user_name))
+                    else:
+                        return redirect('/change/user_name')
+            else:
+                user_name = ip
+
+                curs.execute(db_change("select data from user_set where id = ? and name = 'user_name'"), [ip])
+                db_data = curs.fetchall()
+                if db_data and db_data[0][0] != '':
+                    user_name = db_data[0][0]
+
+                return easy_minify(flask.render_template(skin_check(),
+                    imp = [load_lang('change_user_name'), wiki_set(), wiki_custom(), wiki_css([0, 0])],
+                    data = '''
+                        <form method="post">
+                            <input name="new_user_name" placeholder="''' + load_lang('user_name') + '''" value="''' + html.escape(user_name) + '''">
+                            <hr class="main_hr">
+                            <button id="opennamu_save_button" type="submit">''' + load_lang('save') + '''</button>
+                        </form>
+                    ''',
+                    menu = [['change', load_lang('return')]]
+                ))
+        else:
+            return redirect('/login')

+ 1 - 1
route/view_acl.py

@@ -183,7 +183,7 @@ def view_acl(name):
                     </form>
                 ''',
                 menu = [
-                    ['w/' + url_pas(name), load_lang('document')], 
+                    ['w/' + url_pas(name), load_lang('return')], 
                     ['manager', load_lang('admin')], 
                     ['list/admin/auth_use/' + url_pas('acl (' + name + ')') + '/1', load_lang('acl_record')]
                 ]

+ 40 - 6
route/view_raw.py

@@ -1,10 +1,21 @@
 from .tool.func import *
 
-def view_raw_2(name = None, topic_num = None, num = None, doc_acl = 0):
+from .api_bbs_w_post import api_bbs_w_post
+from .api_bbs_w_comment_one import api_bbs_w_comment_one
+
+def view_raw_2(name = None, topic_num = None, num = None, doc_acl = 0, bbs_num = '', post_num = '', comment_num = ''):
     with get_db_connect() as conn:
         curs = conn.cursor()
+        
+        bbs_num_str = str(bbs_num)
+        post_num_str = str(post_num)
 
-        if topic_num:
+        if bbs_num != '' and post_num != '':
+            if acl_check(bbs_num_str, 'bbs_view') == 1:
+                    return re_error('/ban')
+                    
+            name = ''
+        elif topic_num:
             topic_num = str(topic_num)
             
             if acl_check('', 'topic_view', topic_num) == 1:
@@ -18,9 +29,15 @@ def view_raw_2(name = None, topic_num = None, num = None, doc_acl = 0):
 
         v_name = name
         p_data = ''
-        sub = ' (' + load_lang('raw') + ')'
+        sub = '(' + load_lang('raw') + ')'
 
-        if not topic_num and num:
+        if bbs_num != '' and post_num != '':
+            sub += ' (' + load_lang('bbs') + ')'
+            menu = [['bbs/tool/' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str), load_lang('return')]]
+            
+            if comment_num != '':
+                sub += ' (' + comment_num + ')'
+        elif not topic_num and num:
             curs.execute(db_change("select title from history where title = ? and id = ? and hide = 'O'"), [name, num])
             if curs.fetchall() and admin_check(6) != 1:
                 return re_error('/error/3')
@@ -29,7 +46,7 @@ def view_raw_2(name = None, topic_num = None, num = None, doc_acl = 0):
 
             sub += ' (r' + num + ')'
 
-            menu = [['history/' + url_pas(name), load_lang('history')]]
+            menu = [['history_tool/' + url_pas(num) + '/' + url_pas(name), load_lang('return')]]
         elif topic_num:
             if admin_check(6) != 1:
                 curs.execute(db_change("select data from topic where id = ? and code = ? and block = ''"), [num, topic_num])
@@ -48,7 +65,24 @@ def view_raw_2(name = None, topic_num = None, num = None, doc_acl = 0):
 
             menu = [['w/' + url_pas(name), load_lang('return')]]
 
-        data = curs.fetchall()
+        if bbs_num != '' and post_num != '':
+            if comment_num != '':
+                data = json.loads(api_bbs_w_comment_one(bbs_num_str + '-' + post_num_str + '-' + comment_num).data)
+                sub_data = json.loads(api_bbs_w_post(bbs_num_str + '-' + post_num_str).data)
+            else:
+                data = json.loads(api_bbs_w_post(bbs_num_str + '-' + post_num_str).data)
+                
+            if 'comment' in data:
+                v_name = sub_data["title"]
+                data = [[data["comment"]]]
+            elif 'data' in data:
+                v_name = data["title"]
+                data = [[data["data"]]]
+            else:
+                data = None
+        else:
+            data = curs.fetchall()
+            
         if data:
             p_data += '<textarea readonly class="opennamu_textarea_500">' + html.escape(data[0][0]) + '</textarea>'
             

+ 10 - 3
route/view_read.py

@@ -35,14 +35,21 @@ def view_read(name = 'Test', doc_rev = '', doc_from = '', do_type = ''):
             curs.execute(db_change("select link from back where title = ? and type = 'cat' order by link asc"), [name])
             category_sql = curs.fetchall()
             for data in category_sql:
+                link_view = data[0]
+                if get_main_skin_set(curs, flask.session, 'main_css_category_change_title', ip) != 'off':
+                    curs.execute(db_change("select data from back where title = ? and link = ? and type = 'cat_view' limit 1"), [name, data[0]])
+                    db_data = curs.fetchall()
+                    if db_data and db_data[0][0] != '':
+                        link_view = db_data[0][0]
+
                 if data[0].startswith('category:'):
-                    category_sub += '<li><a href="/w/' + url_pas(data[0]) + '">' + html.escape(data[0]) + '</a></li>'
+                    category_sub += '<li><a href="/w/' + url_pas(data[0]) + '">' + html.escape(link_view) + '</a></li>'
 
                     count_sub_category += 1
                 else:
                     category_doc += '' + \
                         '<li>' + \
-                            '<a href="/w/' + url_pas(data[0]) + '">' + html.escape(data[0]) + '</a> ' + \
+                            '<a href="/w/' + url_pas(data[0]) + '">' + html.escape(link_view) + '</a> ' + \
                             '<a class="opennamu_link_inter" href="/xref/' + url_pas(data[0]) + '">(' + load_lang('backlink') + ')</a>' + \
                         '</li>' + \
                     ''
@@ -188,7 +195,7 @@ def view_read(name = 'Test', doc_rev = '', doc_from = '', do_type = ''):
             response_data = 200
 
         if num != '':
-            menu += [['history/' + url_pas(name), load_lang('history')]]
+            menu += [['history/' + url_pas(name), load_lang('return')]]
             sub = ' (r' + str(num) + ')'
             acl = 0
             r_date = 0

+ 2 - 2
version.json

@@ -1,7 +1,7 @@
 {
     "beta" : {
-        "r_ver" : "v3.4.6-RC3-dev216",
-        "c_ver" : "3500361",
+        "r_ver" : "v3.4.6-RC4-dev228",
+        "c_ver" : "3500365",
         "s_ver" : "3500111"
     }
 }

+ 4 - 0
views/main_css/css/main.css

@@ -381,4 +381,8 @@ s:hover, strike:hover, del:hover {
 
 .opennamu_list_none {
     list-style: none;
+}
+
+#topic_color {
+    background: #bbeabb;
 }

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

@@ -64,4 +64,8 @@ blockquote {
 
 .opennamu_popup_footnote {
     background-color: #313236;
+}
+
+#topic_color {
+    background: #325a56;
 }

+ 2 - 2
views/main_css/js/route/bbs_w_post.js

@@ -1,7 +1,7 @@
 function opennamu_change_comment(get_id) {
     var _a;
     var input = document.querySelector('#opennamu_comment_select');
-    if (input != null) {
+    if (input !== null) {
         input.value = get_id;
         (_a = document.getElementById('opennamu_edit_textarea')) === null || _a === void 0 ? void 0 : _a.focus();
     }
@@ -9,7 +9,7 @@ function opennamu_change_comment(get_id) {
 function opennamu_return_comment() {
     var _a;
     var input = document.querySelector('#opennamu_comment_select');
-    if (input != null) {
+    if (input !== null) {
         (_a = document.getElementById(input.value)) === null || _a === void 0 ? void 0 : _a.focus();
     }
 }

+ 2 - 2
views/main_css/js/route/bbs_w_post.ts

@@ -1,6 +1,6 @@
 function opennamu_change_comment(get_id : string): void {
     const input = document.querySelector('#opennamu_comment_select') as HTMLInputElement | null;
-    if(input != null) {
+    if(input !== null) {
         input.value = get_id;
         document.getElementById('opennamu_edit_textarea')?.focus();
     }
@@ -8,7 +8,7 @@ function opennamu_change_comment(get_id : string): void {
 
 function opennamu_return_comment(): void {
     const input = document.querySelector('#opennamu_comment_select') as HTMLInputElement | null;
-    if(input != null) {
+    if(input !== null) {
         document.getElementById(input.value)?.focus();
     }
 }

+ 1 - 1
views/ringo/index.html

@@ -13,7 +13,7 @@
         <script src="/views/ringo/js/main.js?ver=2"></script>
         <script src="/views/ringo/js/sidebar.js?ver=1"></script>
         <script src="/views/ringo/js/skin_set.js?ver=1"></script>
-        <link rel="stylesheet" href="/views/ringo/css/main.css?ver=3">
+        <link rel="stylesheet" href="/views/ringo/css/main.css?ver=4">
         {% if request.cookies.get('main_css_darkmode', '') == '1' %}
             <link rel="stylesheet" href="/views/main_css/css/sub/dark.css?ver=1">
             <link rel="stylesheet" href="/views/ringo/css/dark.css?ver=1">

+ 3 - 1
views/ringo/js/main.js

@@ -21,7 +21,9 @@ function ringo_opening(data) {
 
         for(for_a in element) {
             if(for_a !== '0') { 
-                document.getElementById(element[for_a]).style.display = 'none';
+                if(document.getElementById(element[for_a]) !== null) {
+                    document.getElementById(element[for_a]).style.display = 'none';
+                }
             }
         }