2
0
Эх сурвалжийг харах

Merge pull request #2042 from openNAMU/dev

디자인 수정 및 칭호 도전과제 패치
잉여개발기 2 жил өмнө
parent
commit
4d412b80b0

+ 7 - 7
app.py

@@ -573,13 +573,13 @@ app.route('/change', methods = ['POST', 'GET'])(user_setting)
 app.route('/change/key')(user_setting_key)
 app.route('/change/key/delete')(user_setting_key_delete)
 app.route('/change/pw', methods = ['POST', 'GET'])(user_setting_pw)
-app.route('/change/head', methods=['GET', 'POST'], defaults = { 'skin_name' : '' })(user_setting_head)
-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/head', methods = ['GET', 'POST'], defaults = { 'skin_name' : '' })(user_setting_head)
+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)
+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
@@ -588,7 +588,7 @@ app.route('/change/skin_set/main', methods = ['POST', 'GET'])(user_setting_skin_
 app.route('/user')(user_info)
 app.route('/user/<name>')(user_info)
 
-app.route('/challenge')(user_challenge)
+app.route('/challenge', methods = ['GET', 'POST'])(user_challenge)
 
 app.route('/count')(user_count)
 app.route('/count/<name>')(user_count)

+ 35 - 7
lang/en-US.json

@@ -119,6 +119,11 @@
         "popup" : "Popup",
         "popover" : "Popover",
         "trace" : "Trace",
+        "level" : "Level",
+        "challenge" : "Challenges",
+        "ua" : "UA",
+        "day" : "Day(s)",
+        "backup" : "Backup",
         "_comment_1.1_" : "Time",
             "second" : "Second(s)",
             "hour" : "Hour(s)",
@@ -215,7 +220,7 @@
         "topic_acl_setting" : "Discussion ACL setting",
         "topic_acl" : "Discussion ACL",
         "delete_admin_group": "Delete admin group",
-        "main_skin_set" : "Main skin setting(s)",
+        "main_skin_set" : "Main skin settings",
         "reset_backlink" : "Reset backlink",
         "link_in_this" : "Links in this document",
         "star_doc" : "Document(s) of interest",
@@ -234,7 +239,6 @@
         "key_change" : "Change key",
         "key_delete" : "Delete key",
         "email_delete" : "Delete email",
-        "challenge" : "Challenge",
         "user_title" : "User title",
         "multiple_ban" : "Multiple ban",
         "dont_move" : "Don't move",
@@ -248,6 +252,9 @@
         "change_user_name" : "Change main user name",
         "sub_user_name" : "Sub user name",
         "start_with_search" : "Search from the first letter",
+        "challenge_and_level_manage" : "Challenges and level management",
+        "bbs_record" : "BBS post(s) record",
+        "bbs_comment_record" : "BBS comment(s) record",
         "_comment_" : "BBS",
             "bbs" : "BBS",
             "bbs_main" : "BBS main",
@@ -398,6 +405,9 @@
                 "move_with_redirect" : "Create a redirect when moving a document",
                 "slow_thread" : "Continuous thread upload limit",
                 "edit_timeout" : "render timeout",
+                "display_level_in_user_name" : "Displaying levels in username",
+                "ua_expiration_date" : "UA Expiration deadline",
+                "document_content_max_length" : "Maximum document length",
             "_comment_" : "Text",
                 "register_text" : "Terms of sign-up",
                 "non_login_alert" : "Non-login alert",
@@ -618,14 +628,32 @@
             "backup_warning" : "Warning. Files with the same name may be erased.",
         "_comment_" : "Challenge",
             "challenge_title_register" : "Hello, World!",
-            "challenge_info_register" : "Sign up",
+            "challenge_info_register" : "Sign up<br><br>Reward : 🌳 title",
     
             "challenge_title_first_contribute" : "Well begun is half done.",
-            "challenge_info_first_contribute" : "Make your 1st contribution",
+            "challenge_info_first_contribute" : "Make your 1st contribution<br><br>Reward : 🔰 title and 500 exp",
     
-            "challenge_title_tenthousandth_contribute" : "Rome wasn't built in a day.",
-            "challenge_info_tenthousandth_contribute" : "Make your 1000th contribution",
+            "challenge_title_tenth_contribute" : "Boys, Be Ambitious!",
+            "challenge_info_tenth_contribute" : "Make your 10th contribution<br><br>Reward : 📝 title and 1000 exp",
+
+            "challenge_title_hundredth_contribute" : "Knowledge is power.",
+            "challenge_info_hundredth_contribute" : "Make your 100th contribution<br><br>보상 : 🖊️ title and 3000 exp",
+
+            "challenge_title_thousandth_contribute" : "Rome wasn't built in a day.",
+            "challenge_info_thousandth_contribute" : "Make your 1000th contribution<br><br>Reward : 🏅 title and 10000 exp",
     
+            "challenge_title_first_discussion" : "Truth is generally the best vindication against slander.",
+            "challenge_info_first_discussion" : "Post the 1st discussion thread<br><br>Reward : 💬 title and 500 exp",
+
+            "challenge_title_tenth_discussion" : "I shall either find a way or make one.",
+            "challenge_info_tenth_discussion" : "Post the 10th discussion thread<br><br>Reward : 💡 title and 1000 exp",
+
+            "challenge_title_hundredth_discussion" : "When all think alike, no one thinks very much.",
+            "challenge_info_hundredth_discussion" : "Post the 100th discussion thread<br><br>Reward : 📢 title and 3000 exp",
+
             "challenge_title_thousandth_discussion" : "I think, therefore I am.",
-            "challenge_info_thousandth_discussion" : "Post the 1000th discussion thread"
+            "challenge_info_thousandth_discussion" : "Post the 1000th discussion thread<br><br>Reward : 📜 title and 10000 exp",
+
+            "challenge_title_admin" : "If you want to test a man's character, give him power.",
+            "challenge_info_admin" : "Be the administrator.<br><br>Reward : ✅ title and 10000 exp"
 }

+ 28 - 6
lang/ko-KR.json

@@ -413,13 +413,25 @@
     "make_new_topic": "새 토론 생성",
     "old_page_warning": "이 문서는 오래되었습니다.",
     "challenge_title_register": "Hello, World!",
-    "challenge_info_register": "가입을 하세요.",
+    "challenge_info_register": "가입을 하세요.<br><br>보상 : 🌳 칭호",
     "challenge_title_first_contribute": "시작이 반이다.",
-    "challenge_info_first_contribute": "첫 기여를 하세요.",
-    "challenge_title_tenthousandth_contribute": "대기만성",
-    "challenge_info_tenthousandth_contribute": "1000번째 기여를 하세요.",
+    "challenge_info_first_contribute": "첫 기여를 하세요.<br><br>보상 : 🔰 칭호와 500 경험치",
+    "challenge_title_tenth_contribute" : "소년이여, 야망을 가져라!",
+    "challenge_info_tenth_contribute" : "10번째 기여를 하세요.<br><br>보상 : 📝 칭호와 1000 경험치",
+    "challenge_title_hundredth_contribute" : "아는 것이 힘이다.",
+    "challenge_info_hundredth_contribute" : "100번째 기여를 하세요.<br><br>보상 : 🖊️ 칭호와 3000 경험치",
+    "challenge_title_thousandth_contribute": "대기만성",
+    "challenge_info_thousandth_contribute": "1000번째 기여를 하세요.<br><br>보상 : 🏅 칭호와 10000 경험치",
+    "challenge_title_first_discussion" : "진실은 보통 모함에 맞서는 최고의 해명이다.",
+    "challenge_info_first_discussion" : "1번째 토론 스레드를 올리세요.<br><br>보상 : 💬 칭호와 500 경험치",
+    "challenge_title_tenth_discussion" : "내가 길을 찾아내거나 직접 길을 만들겠다.",
+    "challenge_info_tenth_discussion" : "10번째 토론 스레드를 올리세요.<br><br>보상 : 💡 칭호와 1000 경험치",
+    "challenge_title_hundredth_discussion" : "모두가 비슷한 생각을 한다는 것은, 아무도 생각하고 있지 않다는 말이다.",
+    "challenge_info_hundredth_discussion" : "100번째 토론 스레드를 올리세요.<br><br>보상 : 📢 칭호와 3000 경험치",
     "challenge_title_thousandth_discussion": "나는 생각한다. 고로 존재한다.",
-    "challenge_info_thousandth_discussion": "1000번째 토론 스레드를 올리세요.",
+    "challenge_info_thousandth_discussion": "1000번째 토론 스레드를 올리세요.<br><br>보상 : 📜 칭호와 10000 경험치",
+    "challenge_title_admin" : "왕후장상의 씨가 어찌 따로 있단 말이냐!",
+    "challenge_info_admin" : "관리자가 되세요.<br><br>보상 : ✅ 칭호와 10000 경험치",
     "challenge": "도전과제",
     "user_title": "칭호",
     "alpha": "알파",
@@ -577,5 +589,15 @@
     "trace": "추적",
     "view_history": "문서 열람 추적 사용",
     "start_with_search" : "첫 글자부터 검색",
-    "backup_warning" : "경고. 동일한 이름의 파일이 있는 경우 지워질 수 있습니다."
+    "backup_warning" : "경고. 동일한 이름의 파일이 있는 경우 지워질 수 있습니다.",
+    "level" : "레벨",
+    "challenge_and_level_manage" : "도전과제와 레벨 관리",
+    "display_level_in_user_name" : "사용자 이름에 레벨 표시",
+    "ua" : "UA",
+    "day" : "일",
+    "ua_expiration_date" : "UA 만료 기한",
+    "backup" : "백업",
+    "document_content_max_length" : "문서 최대 길이",
+    "bbs_record" : "게시판 작성글 목록",
+    "bbs_comment_record" : "게시판 작성댓글 목록"
 }

+ 14 - 0
route/api_user_info.py

@@ -39,6 +39,20 @@ def api_user_info(name = ''):
                     data_result[user_name]['auth_date'] = db_data[0][0]
                 else:
                     data_result[user_name]['auth_date'] = '0'
+
+                curs.execute(db_change("select data from user_set where id = ? and name = 'level'"), [user_name])
+                db_data = curs.fetchall()
+                if db_data:
+                    data_result[user_name]['level'] = db_data[0][0]
+                else:
+                    data_result[user_name]['level'] = '0'
+
+                curs.execute(db_change("select data from user_set where id = ? and name = 'experience'"), [user_name])
+                db_data = curs.fetchall()
+                if db_data:
+                    data_result[user_name]['exp'] = db_data[0][0]
+                else:
+                    data_result[user_name]['exp'] = '0'
                     
                 # ban part
                 if ban_check(name) == 0:

+ 6 - 2
route/main_setting_main.py

@@ -45,7 +45,8 @@ def main_setting_main(db_set):
             41 : ['backup_count', ''],
             42 : ['ua_expiration_date', ''],
             43 : ['auth_history_expiration_date', ''],
-            44 : ['auth_history_off', '']
+            44 : ['auth_history_off', ''],
+            45 : ['user_name_level', '']
         }
 
         if flask.request.method == 'POST':
@@ -92,7 +93,7 @@ def main_setting_main(db_set):
                 else:
                     tls_select += '<option value="' + tls_select_one + '">' + tls_select_one + '</option>'
 
-            check_box_div = [7, 8, '', 20, 23, 24, 25, 26, 31, 33, 34, 35, 36, 37, 44]
+            check_box_div = [7, 8, '', 20, 23, 24, 25, 26, 31, 33, 34, 35, 36, 37, 44, 45]
             for i in range(0, len(check_box_div)):
                 acl_num = check_box_div[i]
                 if acl_num != '' and d_list[acl_num]:
@@ -184,6 +185,9 @@ def main_setting_main(db_set):
                         <input type="checkbox" name="enable_challenge" ''' + check_box_div[6] + '''> ''' + load_lang('enable_challenge_function') + ''' (''' + load_lang('not_working') + ''')
                         <hr class="main_hr">
 
+                        <input type="checkbox" name="user_name_level" ''' + check_box_div[15] + '''> ''' + load_lang('display_level_in_user_name') + '''
+                        <hr class="main_hr">
+
                         <h2>''' + load_lang('design_set') + '''</h2>
                         
                         <span>''' + load_lang('wiki_skin') + '''</span>

+ 50 - 9
route/tool/func.py

@@ -764,16 +764,48 @@ def get_user_title_list(ip = ''):
         # default
         user_title = {
             '' : load_lang('default'),
-            '🌳' : '🌳 namu',
+            '🌳' : '🌳 newbie',
         }
-        
-        # admin
-        if admin_check('all', None, ip) == 1:
-            user_title['✅'] = '✅ admin'
 
         curs.execute(db_change('select name from user_set where id = ? and name = ?'), [ip, 'get_🥚'])
         if curs.fetchall():
             user_title['🥚'] = '🥚 easter_egg'
+
+        curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_first_contribute', ip])
+        if curs.fetchall():
+            user_title['🔰'] = '🔰 first_contribute'
+
+        curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_tenth_contribute', ip])
+        if curs.fetchall():
+            user_title['📝'] = '📝 tenth_contribute'
+
+        curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_hundredth_contribute', ip])
+        if curs.fetchall():
+            user_title['🖊️'] = '🖊️ hundredth_contribute'
+
+        curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_thousandth_contribute', ip])
+        if curs.fetchall():
+            user_title['🏅'] = '🏅 thousandth_contribute'
+
+        curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_first_discussion', ip])
+        if curs.fetchall():
+            user_title['💬'] = '💬 first_discussion'
+
+        curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_tenth_discussion', ip])
+        if curs.fetchall():
+            user_title['💡'] = '💡 tenth_discussion'
+
+        curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_hundredth_discussion', ip])
+        if curs.fetchall():
+            user_title['📢'] = '📢 hundredth_discussion'
+
+        curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_thousandth_discussion', ip])
+        if curs.fetchall():
+            user_title['📜'] = '📜 thousandth_discussion'
+
+        curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_admin', ip])
+        if curs.fetchall():
+            user_title['✅'] = '✅ admin'
         
         return user_title
     
@@ -1013,7 +1045,7 @@ def wiki_css(data):
     data_css = ''
     data_css_add = ''
 
-    data_css_ver = '189'
+    data_css_ver = '191'
     data_css_ver = '.cache_v' + data_css_ver
 
     if 'main_css' in global_wiki_set:
@@ -1400,9 +1432,6 @@ def render_set(doc_name = '', doc_data = '', data_type = 'view', data_in = '', d
                         <style>
                             .table_safe td {
                                 background: transparent !important;
-                            }
-
-                            .table_safe span {
                                 color: inherit !important;
                             }
                         </style>
@@ -2137,6 +2166,18 @@ def ip_pas(raw_ip, type_data = 0):
                     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 and db_data[0][0] != '' else raw_ip
+
+                curs.execute(db_change("select data from other where name = 'user_name_level'"))
+                db_data = curs.fetchall()
+                if db_data and db_data[0][0] != '':
+                    level = '0'
+
+                    curs.execute(db_change("select data from user_set where id = ? and name = 'level'"), [raw_ip])
+                    db_data = curs.fetchall()
+                    if db_data:
+                        level = db_data[0][0]
+
+                    ip += '<sup>' + level + '</sup>'
                 
             if type_data == 0 and change_ip == 0:
                 if is_this_ip == 0:

+ 1 - 1
route/tool/func_render_namumark.py

@@ -2307,7 +2307,7 @@ class class_do_render_namumark:
             if self.doc_include != '' or re.search(r'<toc_no_auto>', self.render_data) or toc_set_data == 'half_off' or toc_set_data == 'off' or toc_data_on == 1:
                 self.render_data = self.render_data.replace('<toc_no_auto>', '')
             else:
-                self.render_data = re.sub(r'(?P<in><h[1-6] id="[^"]*">)', self.data_toc + '\\g<in>', self.render_data, 1)
+                self.render_data = re.sub(r'(?P<in><h[1-6] id="[^"]*">)', '<br>' + self.data_toc + '\\g<in>', self.render_data, 1)
         else:
             self.render_data = self.render_data.replace('<toc_need_part>', '')
             self.render_data = self.render_data.replace('<toc_no_auto>', '')

+ 230 - 141
route/user_challenge.py

@@ -30,146 +30,235 @@ def user_challenge():
         if ip_or_user(ip) == 1:
             return redirect('/user')
 
-        data_html_green = ''
-        data_html_red = ''
-        
-        data_html_green += do_make_challenge_design(
-            '🆕',
-            load_lang('challenge_title_register'), 
-            load_lang('challenge_info_register'),
-            1
-        )
-        
-        curs.execute(db_change('select count(*) from history where ip = ?'), [ip])
-        db_data = curs.fetchall()
-        
-        disable = 1 if db_data[0][0] >= 1 else 0
-        data_html = do_make_challenge_design(
-            '✏',
-            load_lang('challenge_title_first_contribute'), 
-            load_lang('challenge_info_first_contribute'),
-            disable
-        )
-        if disable == 1:
-            data_html_green += data_html
-        else:
-            data_html_red += data_html
-        
-        disable = 1 if db_data[0][0] >= 10 else 0
-        data_html = do_make_challenge_design(
-            '🗊',
-            load_lang('challenge_title_tenth_contribute'), 
-            load_lang('challenge_info_tenth_contribute'),
-            disable
-        )
-        if disable == 1:
-            data_html_green += data_html
-        else:
-            data_html_red += data_html
-        
-        disable = 1 if db_data[0][0] >= 100 else 0
-        data_html = do_make_challenge_design(
-            '🗀',
-            load_lang('challenge_title_hundredth_contribute'), 
-            load_lang('challenge_info_hundredth_contribute'),
-            disable
-        )
-        if disable == 1:
-            data_html_green += data_html
-        else:
-            data_html_red += data_html
-        
-        disable = 1 if db_data[0][0] >= 1000 else 0
-        data_html = do_make_challenge_design(
-            '🖪',
-            load_lang('challenge_title_thousandth_contribute'), 
-            load_lang('challenge_info_thousandth_contribute'),
-            disable
-        )
-        if disable == 1:
-            data_html_green += data_html
-        else:
-            data_html_red += data_html
-        
-        disable = 1 if db_data[0][0] >= 10000 else 0
-        data_html = do_make_challenge_design(
-            '🖴',
-            load_lang('challenge_title_tenthousandth_contribute'), 
-            load_lang('challenge_info_tenthousandth_contribute'),
-            disable
-        )
-        if disable == 1:
-            data_html_green += data_html
-        else:
-            data_html_red += data_html
-        
-        curs.execute(db_change("select count(*) from topic where ip = ?"), [ip])
-        db_data = curs.fetchall()
-        
-        disable = 1 if db_data[0][0] >= 1 else 0
-        data_html = do_make_challenge_design(
-            '🗨',
-            load_lang('challenge_title_first_discussion'), 
-            load_lang('challenge_info_first_discussion'),
-            disable
-        )
-        if disable == 1:
-            data_html_green += data_html
-        else:
-            data_html_red += data_html
-        
-        disable = 1 if db_data[0][0] >= 10 else 0
-        data_html = do_make_challenge_design(
-            '🗪',
-            load_lang('challenge_title_tenth_discussion'), 
-            load_lang('challenge_info_tenth_discussion'),
-            disable
-        )
-        if disable == 1:
-            data_html_green += data_html
-        else:
-            data_html_red += data_html
-        
-        disable = 1 if db_data[0][0] >= 100 else 0
-        data_html = do_make_challenge_design(
-            '🖅',
-            load_lang('challenge_title_hundredth_discussion'), 
-            load_lang('challenge_info_hundredth_discussion'),
-            disable
-        )
-        if disable == 1:
-            data_html_green += data_html
-        else:
-            data_html_red += data_html
-        
-        disable = 1 if db_data[0][0] >= 1000 else 0
-        data_html = do_make_challenge_design(
-            '☏',
-            load_lang('challenge_title_thousandth_discussion'), 
-            load_lang('challenge_info_thousandth_discussion'),
-            disable
-        )
-        if disable == 1:
-            data_html_green += data_html
-        else:
-            data_html_red += data_html
-        
-        disable = 1 if db_data[0][0] >= 10000 else 0
-        data_html = do_make_challenge_design(
-            '🖧',
-            load_lang('challenge_title_tenthousandth_discussion'), 
-            load_lang('challenge_info_tenthousandth_discussion'),
-            disable
-        )
-        if disable == 1:
-            data_html_green += data_html
+        if flask.request.method == 'POST':
+            user_exp = 0
+
+            curs.execute(db_change('select count(*) from history where ip = ?'), [ip])
+            db_data = curs.fetchall()
+            if not db_data:
+                db_data = [[0]]
+
+            user_exp += 5 * db_data[0][0]
+
+            if db_data[0][0] >= 1:
+                curs.execute(db_change("delete from user_set where id = ? and name = 'challenge_first_contribute'"), [ip])
+                curs.execute(db_change("insert into user_set (name, id, data) values ('challenge_first_contribute', ?, '1')"), [ip])
+                user_exp += 500
+
+            if db_data[0][0] >= 10:
+                curs.execute(db_change("delete from user_set where id = ? and name = 'challenge_tenth_contribute'"), [ip])
+                curs.execute(db_change("insert into user_set (name, id, data) values ('challenge_tenth_contribute', ?, '1')"), [ip])
+                user_exp += 1000
+
+            if db_data[0][0] >= 100:
+                curs.execute(db_change("delete from user_set where id = ? and name = 'challenge_hundredth_contribute'"), [ip])
+                curs.execute(db_change("insert into user_set (name, id, data) values ('challenge_hundredth_contribute', ?, '1')"), [ip])
+                user_exp += 3000        
+
+            if db_data[0][0] >= 1000:
+                curs.execute(db_change("delete from user_set where id = ? and name = 'challenge_thousandth_contribute'"), [ip])
+                curs.execute(db_change("insert into user_set (name, id, data) values ('challenge_thousandth_contribute', ?, '1')"), [ip])
+                user_exp += 10000
+
+            curs.execute(db_change("select count(*) from topic where ip = ?"), [ip])
+            db_data = curs.fetchall()
+            if not db_data:
+                db_data = [[0]]
+
+            user_exp += 5 * db_data[0][0]
+
+            if db_data[0][0] >= 1:
+                curs.execute(db_change("delete from user_set where id = ? and name = 'challenge_first_discussion'"), [ip])
+                curs.execute(db_change("insert into user_set (name, id, data) values ('challenge_first_discussion', ?, '1')"), [ip])
+                user_exp += 500    
+
+            if db_data[0][0] >= 10:
+                curs.execute(db_change("delete from user_set where id = ? and name = 'challenge_tenth_discussion'"), [ip])
+                curs.execute(db_change("insert into user_set (name, id, data) values ('challenge_tenth_discussion', ?, '1')"), [ip])
+                user_exp += 1000
+
+            if db_data[0][0] >= 100:
+                curs.execute(db_change("delete from user_set where id = ? and name = 'challenge_hundredth_discussion'"), [ip])
+                curs.execute(db_change("insert into user_set (name, id, data) values ('challenge_hundredth_discussion', ?, '1')"), [ip])
+                user_exp += 3000
+
+            if db_data[0][0] >= 1000:
+                curs.execute(db_change("delete from user_set where id = ? and name = 'challenge_thousandth_discussion'"), [ip])
+                curs.execute(db_change("insert into user_set (name, id, data) values ('challenge_thousandth_discussion', ?, '1')"), [ip])
+                user_exp += 10000        
+
+            curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_admin', ip])
+            db_data = curs.fetchall()
+            if admin_check('all') == 1 or db_data:
+                curs.execute(db_change("delete from user_set where id = ? and name = 'challenge_admin'"), [ip])
+                curs.execute(db_change("insert into user_set (name, id, data) values ('challenge_admin', ?, '1')"), [ip])
+                user_exp += 10000
+
+            exp = user_exp
+            level = 0
+            while 1:
+                if exp >= (500 + level * 50):
+                    exp -= (500 + level * 50)
+                    level += 1
+                else:
+                    break
+
+            curs.execute(db_change("delete from user_set where id = ? and name = 'level'"), [ip])
+            curs.execute(db_change("insert into user_set (name, id, data) values ('level', ?, ?)"), [ip, level])
+
+            curs.execute(db_change("delete from user_set where id = ? and name = 'experience'"), [ip])
+            curs.execute(db_change("insert into user_set (name, id, data) values ('experience', ?, ?)"), [ip, exp])
+
+            return redirect('/challenge')
         else:
-            data_html_red += data_html
+            data_html_green = ''
+            data_html_red = ''
             
-        data_html = data_html_green + data_html_red
-        
-        return easy_minify(flask.render_template(skin_check(),
-            imp = [load_lang('challenge'), wiki_set(), wiki_custom(), wiki_css([0, 0])],
-            data = data_html,
-            menu = [['user', load_lang('return')]]
-        ))
+            data_html_green += do_make_challenge_design(
+                '🌳',
+                load_lang('challenge_title_register'), 
+                load_lang('challenge_info_register', 1),
+                1
+            )
+            
+            curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_first_contribute', ip])
+            db_data = curs.fetchall()
+            disable = 1 if db_data else 0
+            data_html = do_make_challenge_design(
+                '🔰',
+                load_lang('challenge_title_first_contribute'), 
+                load_lang('challenge_info_first_contribute', 1),
+                disable
+            )
+            if disable == 1:
+                data_html_green += data_html
+            else:
+                data_html_red += data_html
+            
+            curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_tenth_contribute', ip])
+            db_data = curs.fetchall()
+            disable = 1 if db_data else 0
+            data_html = do_make_challenge_design(
+                '📝',
+                load_lang('challenge_title_tenth_contribute'), 
+                load_lang('challenge_info_tenth_contribute', 1),
+                disable
+            )
+            if disable == 1:
+                data_html_green += data_html
+            else:
+                data_html_red += data_html
+            
+            curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_hundredth_contribute', ip])
+            db_data = curs.fetchall()
+            disable = 1 if db_data else 0
+            data_html = do_make_challenge_design(
+                '🖊️',
+                load_lang('challenge_title_hundredth_contribute'), 
+                load_lang('challenge_info_hundredth_contribute', 1),
+                disable
+            )
+            if disable == 1:
+                data_html_green += data_html
+            else:
+                data_html_red += data_html
+            
+            curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_thousandth_contribute', ip])
+            db_data = curs.fetchall()
+            disable = 1 if db_data else 0
+            data_html = do_make_challenge_design(
+                '🏅',
+                load_lang('challenge_title_thousandth_contribute'), 
+                load_lang('challenge_info_thousandth_contribute', 1),
+                disable
+            )
+            if disable == 1:
+                data_html_green += data_html
+            else:
+                data_html_red += data_html
+            
+            curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_first_discussion', ip])
+            db_data = curs.fetchall()
+            disable = 1 if db_data else 0
+            data_html = do_make_challenge_design(
+                '💬',
+                load_lang('challenge_title_first_discussion'), 
+                load_lang('challenge_info_first_discussion', 1),
+                disable
+            )
+            if disable == 1:
+                data_html_green += data_html
+            else:
+                data_html_red += data_html
+            
+            curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_tenth_discussion', ip])
+            db_data = curs.fetchall()
+            disable = 1 if db_data else 0
+            data_html = do_make_challenge_design(
+                '💡',
+                load_lang('challenge_title_tenth_discussion'), 
+                load_lang('challenge_info_tenth_discussion', 1),
+                disable
+            )
+            if disable == 1:
+                data_html_green += data_html
+            else:
+                data_html_red += data_html
+            
+            curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_hundredth_discussion', ip])
+            db_data = curs.fetchall()
+            disable = 1 if db_data else 0
+            data_html = do_make_challenge_design(
+                '📢',
+                load_lang('challenge_title_hundredth_discussion'), 
+                load_lang('challenge_info_hundredth_discussion', 1),
+                disable
+            )
+            if disable == 1:
+                data_html_green += data_html
+            else:
+                data_html_red += data_html
+            
+            curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_thousandth_discussion', ip])
+            db_data = curs.fetchall()
+            disable = 1 if db_data else 0
+            data_html = do_make_challenge_design(
+                '📜',
+                load_lang('challenge_title_thousandth_discussion'), 
+                load_lang('challenge_info_thousandth_discussion', 1),
+                disable
+            )
+            if disable == 1:
+                data_html_green += data_html
+            else:
+                data_html_red += data_html
+                
+            data_html = data_html_green + data_html_red
+
+            curs.execute(db_change('select data from user_set where name = ? and id = ?'), ['challenge_admin', ip])
+            db_data = curs.fetchall()
+            disable = 1 if db_data else 0
+            data_html = do_make_challenge_design(
+                '✅',
+                load_lang('challenge_title_admin'), 
+                load_lang('challenge_info_admin', 1),
+                disable
+            )
+            if disable == 1:
+                data_html_green += data_html
+            else:
+                data_html_red += data_html
+                
+            data_html = data_html_green + data_html_red
+            
+            return easy_minify(flask.render_template(skin_check(),
+                imp = [load_lang('challenge_and_level_manage'), wiki_set(), wiki_custom(), wiki_css([0, 0])],
+                data = data_html + '''
+                    <form method="post">
+                        <div id="opennamu_get_user_info">''' + html.escape(ip) + '''</div>
+                        <hr class="main_hr">
+                        <button id="opennamu_save_button" type="submit">''' + load_lang('reload') + '''</button>
+                    </form>
+                ''',
+                menu = [['user', load_lang('return')]]
+            ))

+ 1 - 1
route/user_info.py

@@ -28,7 +28,7 @@ def user_info(name = ''):
     
                 tool_menu += '<li><a href="/watch_list">' + load_lang('watchlist') + '</a></li>'
                 tool_menu += '<li><a href="/star_doc">' + load_lang('star_doc') + '</a></li>'
-                tool_menu += '<li><a href="/challenge">' + load_lang('challenge') + '</a></li>'
+                tool_menu += '<li><a href="/challenge">' + load_lang('challenge_and_level_manage') + '</a></li>'
                 tool_menu += '<li><a href="/acl/user:' + url_pas(ip) + '">' + load_lang('user_document_acl') + '</a></li>'
             else:
                 login_menu += '''

+ 3 - 2
route/user_setting.py

@@ -16,6 +16,8 @@ def user_setting():
                     ['user_title', flask.request.form.get('user_title', '')],
                     ['sub_user_name' , flask.request.form.get('sub_user_name', '')]
                 ]
+                if not auto_list[2][1] in get_user_title_list(ip):
+                    auto_list[2][1] = ''
 
                 twofa_on = flask.request.form.get('2fa', '')
                 if twofa_on != '':
@@ -67,11 +69,10 @@ def user_setting():
                     else:
                         div3 += '<option value="' + lang_data + '">' + see_data + '</option>'
 
-                # 여기 잘못 짬
                 curs.execute(db_change('select data from user_set where name = "user_title" and id = ?'), [ip])
                 data = curs.fetchall()
                 data = [['']] if not data else data
-                user_title_list = get_user_title_list()
+                user_title_list = get_user_title_list(ip)
                 div4 = ''
                 for user_title in user_title_list:                
                     if data and data[0][0] == user_title:

+ 1 - 1
version.json

@@ -1,6 +1,6 @@
 {
     "beta" : {
-        "r_ver" : "v3.4.6-RC5-dev82",
+        "r_ver" : "v3.4.6-RC5-dev87",
         "c_ver" : "3500374",
         "s_ver" : "3500112"
     }

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

@@ -293,18 +293,16 @@ hr {
 }
 
 blockquote {
-    padding: 15px 40px 15px 15px;
+    padding: 15px;
+    
     margin: 0;
+    margin-top: 10px;
+    margin-bottom: 10px;
 
     display: inline-block;
 
     border: 2px solid #ccc;
     border-left: 5px solid black;
-
-    background-image: url('/views/main_css/file/quote.png');
-    background-position: calc(100% - 10px) 10px;
-    background-repeat: no-repeat;
-    background-size: 25px;
 }
 
 /* 취소선 */

+ 5 - 41
views/main_css/js/func/func.js

@@ -3,114 +3,78 @@
 // https://css-tricks.com/how-to-animate-the-details-element/
 class Accordion {
     constructor(el) {
-        // Store the <details> element
         this.el = el;
-        // Store the <summary> element
         this.summary = el.querySelector('summary');
-        // Store the <div class="content"> element
         this.content = el.querySelector('.opennamu_folding');
     
-        // Store the animation object (so we can cancel it if needed)
         this.animation = null;
-        // Store if the element is closing
         this.isClosing = false;
-        // Store if the element is expanding
         this.isExpanding = false;
-        // Detect user clicks on the summary element
         this.summary.addEventListener('click', (e) => this.onClick(e));
     }
   
     onClick(e) {
-        // Stop default behaviour from the browser
         e.preventDefault();
-        // Add an overflow on the <details> to avoid content overflowing
         this.el.style.overflow = 'hidden';
-        // Check if the element is being closed or is already closed
         if(this.isClosing || !this.el.open) {
             this.open();
-        // Check if the element is being openned or is already open
         } else if(this.isExpanding || this.el.open) {
             this.shrink();
         }
     }
   
     shrink() {
-        // Set the element as "being closed"
         this.isClosing = true;
         
-        // Store the current height of the element
         const startHeight = `${this.el.offsetHeight}px`;
-        // Calculate the height of the summary
         const endHeight = `${this.summary.offsetHeight}px`;
         
-        // If there is already an animation running
         if(this.animation) {
-            // Cancel the current animation
             this.animation.cancel();
         }
         
-        // Start a WAAPI animation
         this.animation = this.el.animate({
-        // Set the keyframes from the startHeight to endHeight
             height: [startHeight, endHeight]
         }, {
             duration: 200,
             easing: 'ease-out'
         });
         
-        // When the animation is complete, call onAnimationFinish()
         this.animation.onfinish = () => this.onAnimationFinish(false);
-        // If the animation is cancelled, isClosing variable is set to false
         this.animation.oncancel = () => this.isClosing = false;
     }
   
     open() {
-        // Apply a fixed height on the element
         this.el.style.height = `${this.el.offsetHeight}px`;
-        // Force the [open] attribute on the details element
         this.el.open = true;
-        // Wait for the next frame to call the expand function
         window.requestAnimationFrame(() => this.expand());
     }
   
     expand() {
-        // Set the element as "being expanding"
         this.isExpanding = true;
-        // Get the current fixed height of the element
         const startHeight = `${this.el.offsetHeight}px`;
-        // Calculate the open height of the element (summary height + content height)
         const endHeight = `${this.summary.offsetHeight + this.content.offsetHeight}px`;
         
-        // If there is already an animation running
         if(this.animation) {
-            // Cancel the current animation
             this.animation.cancel();
         }
         
-        // Start a WAAPI animation
         this.animation = this.el.animate({
-        // Set the keyframes from the startHeight to endHeight
             height: [startHeight, endHeight]
         }, {
             duration: 200,
             easing: 'ease-out'
         });
-        // When the animation is complete, call onAnimationFinish()
         this.animation.onfinish = () => this.onAnimationFinish(true);
-        // If the animation is cancelled, isExpanding variable is set to false
         this.animation.oncancel = () => this.isExpanding = false;
     }
   
     onAnimationFinish(open) {
-      // Set the open attribute based on the parameter
-      this.el.open = open;
-      // Clear the stored animation
-      this.animation = null;
-      // Reset isClosing & isExpanding
-      this.isClosing = false;
-      this.isExpanding = false;
-      // Remove the overflow hidden and the fixed height
-      this.el.style.height = this.el.style.overflow = '';
+        this.el.open = open;
+        this.animation = null;
+        this.isClosing = false;
+        this.isExpanding = false;
+        this.el.style.height = this.el.style.overflow = '';
     }
 }
 

+ 15 - 2
views/main_css/js/func/insert_user_info.js

@@ -18,7 +18,8 @@ function do_insert_user_info() {
             'why',
             'band_blocked',
             'ip',
-            'ban'
+            'ban',
+            'level'
         ];
 
         let data_form = new FormData();
@@ -40,7 +41,6 @@ function do_insert_user_info() {
                     if(this.readyState === 4 && this.status === 200) {
                         let get_data = JSON.parse(this.responseText);
                         
-                        // 한글 지원 필요
                         let get_data_auth = get_data[name]['auth'];
                         if(get_data_auth === '0') {
                             get_data_auth = lang_data['ip'];
@@ -88,6 +88,15 @@ function do_insert_user_info() {
                             
                             get_data_ban += lang_data['why'] + ' : ' + get_data[name]['ban']['reason'];
                         }
+
+                        let level = '0';
+                        let exp = '0';
+                        let max_exp = '0';
+                        if(get_data_auth !== lang_data['ip']) {
+                            level = get_data[name]['level'];
+                            exp = get_data[name]['exp'];
+                            max_exp = String(500 + (Number(get_data[name]['level']) * 50));
+                        }
                         
                         let data = '' +
                             '<table class="user_info_table">' +
@@ -103,6 +112,10 @@ function do_insert_user_info() {
                                     '<td>' + lang_data['state'] + '</td>' +
                                     '<td>' + get_data_ban + '</td>' +
                                 '</tr>' +
+                                '<tr>' +
+                                    '<td>' + lang_data['level'] + '</td>' +
+                                    '<td>' + level + ' (' + exp + ' / ' + max_exp + ')</td>' +
+                                '</tr>' +
                             '</table>' +
                         '';
                         

+ 2 - 0
views/ringo/css/main.css

@@ -284,6 +284,8 @@ article.main#main_data {
 
 article.main#title h1 {
     margin: 0;
+
+    font-size: 32px;
 }
 
 .only_mobile, header#main form.only_mobile {

+ 4 - 19
views/ringo/index.html

@@ -12,28 +12,13 @@
         <script src="https://code.iconify.design/1/1.0.3/iconify.min.js"></script>
         <script src="/views/ringo/js/main.js.cache_v2"></script>
         <script src="/views/ringo/js/sidebar.js.cache_v1"></script>
-        <script src="/views/ringo/js/skin_set.js.cache_v2"></script>
-        <link rel="stylesheet" href="/views/ringo/css/main.css.cache_v6">
+        <script src="/views/ringo/js/skin_set.js.cache_v5"></script>
+        <link rel="stylesheet" href="/views/ringo/css/main.css.cache_v7">
         {% if request.cookies.get('main_css_darkmode', '') == '1' %}
             <link rel="stylesheet" href="/views/main_css/css/sub/dark.css.cache_v1">
             <link rel="stylesheet" href="/views/ringo/css/dark.css.cache_v1">
         {% endif %}
-        {% if request.cookies.get('main_css_off_sidebar', '') == '1' %}
-            <style>
-                section {
-                    display: block;
-                    margin: auto;
-                }
-
-                .aside_width {
-                    display: none;
-                }
-
-                .do_fixed {
-                    display: none;
-                }
-            </style>
-        {% endif %}
+        <style id="ringo_add_style"></style>
         <link rel="shortcut icon" href="/views/main_css/file/favicon.ico.cache_v1">
         {{imp[1][5]|safe}}
         {{imp[2][3]|safe}}
@@ -246,7 +231,7 @@
                 </article>
             </footer>
         </section>
-        <div class="aside_width">A</div>
+        <div class="aside_width"> </div>
         <aside class="do_fixed">
             <button id="side_button_1" class="side_button selected">{{'edit'|load_lang}}</button><!--
          --><button id="side_button_2" class="side_button">{{'discussion'|load_lang}}</button><!--

+ 1 - 1
views/ringo/info.json

@@ -1,5 +1,5 @@
 {
     "name" : "Ringo",
-    "skin_ver" : "v1.0.8",
+    "skin_ver" : "v1.1.2",
     "require_ver" : "3500112"
 }

+ 70 - 7
views/ringo/js/skin_set.js

@@ -12,16 +12,23 @@ function ringo_get_post() {
 
     const check_2 = document.getElementById('use_sys_darkmode');
     if(check_2.checked === true) {
-        document.cookie = 'main_css_use_sys_darkmode=1; path=/';
+        window.localStorage.setItem('main_css_use_sys_darkmode', '1');
     } else {
-        document.cookie = 'main_css_use_sys_darkmode=0; path=/';
+        window.localStorage.setItem('main_css_use_sys_darkmode', '0');
     }
 
     const check_3 = document.getElementById('off_sidebar');
     if(check_3.checked === true) {
-        document.cookie = 'main_css_off_sidebar=1; path=/';
+        window.localStorage.setItem('main_css_off_sidebar', '1');
     } else {
-        document.cookie = 'main_css_off_sidebar=0; path=/';
+        window.localStorage.setItem('main_css_off_sidebar', '0');
+    }
+
+    const check_4 = document.getElementById('fixed_width');
+    if(check_4.options[check_4.selectedIndex].value) {
+        window.localStorage.setItem('main_css_fixed_width', check_4.options[check_4.selectedIndex].value);
+    } else {
+        window.localStorage.setItem('main_css_fixed_width', '');
     }
 
     history.go(0);
@@ -29,13 +36,41 @@ function ringo_get_post() {
 
 function ringo_do_skin_set() {
     let cookies = document.cookie;
-    if(!cookies.match(ringo_do_regex_data('main_css_use_sys_darkmode')) || (cookies.match(ringo_do_regex_data('main_css_use_sys_darkmode')) && cookies.match(ringo_do_regex_data('main_css_use_sys_darkmode'))[1] === '1')) {
+    
+    if(!window.localStorage.getItem('main_css_use_sys_darkmode') || window.localStorage.getItem('main_css_use_sys_darkmode') === '1') {
         if(window.matchMedia('(prefers-color-scheme: dark)').matches) {
             document.cookie = 'main_css_darkmode=1; path=/';
         } else {
             document.cookie = 'main_css_darkmode=0; path=/';
         }
     }
+
+    if(window.localStorage.getItem('main_css_off_sidebar') && window.localStorage.getItem('main_css_off_sidebar') === '1') {
+        document.getElementById('ringo_add_style').innerHTML += `
+            section {
+                width: auto;
+                display: block;
+                margin: auto;
+            }
+
+            .aside_width {
+                display: none;
+            }
+
+            .do_fixed {
+                display: none;
+            }
+        `;
+    }
+
+    if(window.localStorage.getItem('main_css_fixed_width') && window.localStorage.getItem('main_css_fixed_width') !== '') {
+        let fixed_width_data = window.localStorage.getItem('main_css_fixed_width');
+        document.getElementById('ringo_add_style').innerHTML += `
+            article.main {
+                max-width: ` + fixed_width_data + `px;
+            }
+        `;
+    }
 }
 
 function ringo_load_skin_set() {
@@ -48,11 +83,15 @@ function ringo_load_skin_set() {
                 "darkmode" : "Darkmode",
                 "use_sys_darkmode" : "Use system darkmode set",
                 "off_sidebar" : "Turn off sidebar",
+                "fixed_width" : "Fixed width",
+                'default' : 'Default',
             }, "ko-KR" : {
                 "save" : "저장",
                 "darkmode" : "다크모드",
                 "use_sys_darkmode" : "시스템 다크모드 설정 사용",
                 "off_sidebar" : "사이드바 끄기",
+                "fixed_width" : "고정폭",
+                'default' : '기본값',
             }
         }
 
@@ -72,14 +111,36 @@ function ringo_load_skin_set() {
             set_data["invert"] = "checked";
         }
 
-        if(!cookies.match(ringo_do_regex_data('main_css_use_sys_darkmode')) || (cookies.match(ringo_do_regex_data('main_css_use_sys_darkmode')) && cookies.match(ringo_do_regex_data('main_css_use_sys_darkmode'))[1] === '1')) {
+        if(!window.localStorage.getItem('main_css_use_sys_darkmode') || window.localStorage.getItem('main_css_use_sys_darkmode') === '1') {
             set_data["use_sys_darkmode"] = "checked";
         }
 
-        if(cookies.match(ringo_do_regex_data('main_css_off_sidebar')) && cookies.match(ringo_do_regex_data('main_css_off_sidebar'))[1] === '1') {
+        if(window.localStorage.getItem('main_css_off_sidebar') && window.localStorage.getItem('main_css_off_sidebar') === '1') {
             set_data["off_sidebar"] = "checked";
         }
 
+        let fixed_width_data = '';
+        if(window.localStorage.getItem('main_css_fixed_width')) {
+            fixed_width_data = window.localStorage.getItem('main_css_fixed_width');
+        }
+
+        let select_fixed_width = [set_language[language]['default'], '800', '900', '1000', '1100', '1200', '1300', '1500', '1600'];
+        let select_fixed_width_html = '<select name="fixed_width" id="fixed_width">';
+        for(let for_a = 0; for_a < select_fixed_width.length; for_a++) {
+            let for_a_data = select_fixed_width[for_a];
+            if(for_a_data === set_language[language]['default']) {
+                for_a_data = '';
+            }
+
+            let selected = '';
+            if(fixed_width_data === for_a_data) {
+                selected = 'selected';
+            }
+
+            select_fixed_width_html += '<option value="' + for_a_data + '" ' + selected + '>' + select_fixed_width[for_a] + '</option>';
+        }
+        select_fixed_width_html += '</select>';
+
         document.getElementById("main_skin_set").innerHTML = ' \
             <input ' + set_data["use_sys_darkmode"] + ' type="checkbox" id="use_sys_darkmode" name="use_sys_darkmode" value="use_sys_darkmode"> ' + set_language[language]['use_sys_darkmode'] + ' \
             <hr class="main_hr"> \
@@ -87,6 +148,8 @@ function ringo_load_skin_set() {
             <hr class="main_hr"> \
             <input ' + set_data["off_sidebar"] + ' type="checkbox" id="off_sidebar" name="off_sidebar" value="off_sidebar"> ' + set_language[language]['off_sidebar'] + ' \
             <hr class="main_hr"> \
+            ' + select_fixed_width_html + ' \
+            <hr class="main_hr"> \
             <button onclick="ringo_get_post();">' + set_language[language]['save'] + '</button> \
         ';
     }