From 55210ddeb82132d5478e162cb41c90e7c03dc02c Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Sat, 2 Nov 2024 22:13:38 +1100 Subject: [PATCH 1/6] Add template url --- data/db.sqlite3 | Bin 192512 -> 204800 bytes links/forms.py | 24 ++++++++ .../0008_alter_link_original_url.py | 18 ++++++ links/models.py | 52 +++++++++++++++++- links/templates/links/link_form.html | 8 +++ links/urls.py | 1 + links/views.py | 33 +++++++++-- locale/zh_Hans/LC_MESSAGES/django.mo | Bin 3744 -> 3960 bytes locale/zh_Hans/LC_MESSAGES/django.po | 5 +- new_theme/static/css/dist/styles.css | 15 +++++ 10 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 links/migrations/0008_alter_link_original_url.py diff --git a/data/db.sqlite3 b/data/db.sqlite3 index 60f3aa4c9d05aef6f63669ac7da13dfccf25c90b..a6f7eda27cd66f2dd324d23abf4d46e945442347 100644 GIT binary patch delta 6695 zcmeG>X>b!~mR~=~lKr)%mhpvc4j&lXF_tCSvLzeGHVh$pr~oppZS^gzu#} z{(VME6Xf`hc3Wz5mUWe8k>VBIVJR{ax7lnij~Gq_Vjkt)6OBnhDI$hF$ynH7b2K;D z?F}|Zx!vAoYi@J8t*thf%i%owaLIN@S|9 zYvR&u(jR;wF%k@fqd~opud9G70P?^bldf(S(-)R}BZySDFV%PlS9-JUn2pn0ba~_s zX6)h?>bZINbX6DUp|WMZv3OkrC)=4P$`#FAh;R$InU}h`wgpZ4aWaQ%AwsiRGIi&U z@TYkj{d3ORoU&|hcD3;y!vTXuXNUW^maKQkr}#&>+Gkqdg&q332c0%IjDc_5@hMBW|ZZ|ixewd48bMz>8 z?wfVTPICG#T`fIxzY&(3`Tcy7-@rR~3;l|oLBAyEba|bgbi;QYiN0*IS#55Y)7eTM zD6joN411H++3a>YoQP5L0~oe8N3+%GXmQ(Zgq1J;YYdyK&DJt)bvs;chnuj4)ri7< zR}fn(29?#}Y<4*u`h|;r0HLML*=lt)*_^hfXnEBSFllMC+pVomcZ=JJVpLwiur=6S z<&K$Gd1%wms}-_{@MqxCFN{Tb@@12Lyynz+!J8?=0X)>9i zHFg17srM(8RAq1vU4Six;&MtVy1R3bZL2hvNX4Vcn2(+R8RWCQw}PInz6B~--v>}w z5LiaZ{Paq)T)}LJ#)9np2T;TgDL6A9K$$Swttl~pIP7c?!Sip0Dpv9#)X;%0N=h=! zSA7V*=1rZHP5j0{gUGwvf)du$J? zed!bEvGln#aN}?|Igufk`YCFA`=^jcM_N^>*n^*fpU!XLNp(g&^JlQ17CU)W-N!zI zdkZI;RlkwouoMq9gr#_bZLdN<2R?`O)Ze7?W6ypLgLzSh=EtF7DIADra67+%>nweC z6_+#)i%6#DOH}pbolv(G5v%kR*XroetDk7`D2%=?Z zi6&ONKPHZ6#CrK|G}*shgkp=QUW0e~qvMebT=)vwg`umiNa>Hv>N@%rES-~6$Ir$$ zkp;gb#wIg{u-uDc{Kr?&O}(|0lw~Zx^b%~LQjHp`?BFF3%Q0V-e+Il3<5h=O4PLc) zS@EixDer=F(e+vQtQWq(J8@v0`7hzJ@P(-&_c;Fr|0}+cK1y%Mxty~%Cp-IR*>>Y& z#-)aL4HH>^%(^G5On*ebUiZ1~PTW0DK?iq%8z*0r`*0^(Mm>vFmD$Ndu$gkz{A{AL zio-Bzkrt^YBt5=pSjuX-^GPacM!(EciIz3B+tVBspixOsDFVbg5?PqG;f`+tB+DyK=>JqZb$IgyG0ZU^X>qCs+ z9D@aPs942d|8opBnxjRUq&_d^+G5n}U=3pLpMfQmE7Zc|wHcVCeFd7HYyJh+%3CJ7nLFkQW4u-he(P;ES@}-D(Un} zVJQQDPl12(6qJ}emT$q?TM}mH-dfcA>Z=fA<l!A?HO2{yDC>T6)9u~2QdS!VmK_X<5imM zn`hu9>ZNHy9EQ`I{|Z}#!5nQ_yXcLxp5s_8)*c5J6|>XUQZVm54*hh>sOrToABQ*{ zG^Djq;G@rC=Kk$^C}rENP@L<{N*6}1&_vwq%(JkB?dd@i9`-;H-J+*N=5R^hD?(~ppSw1&_@M!R5&>tTDk zU`rdV;g9l<@&{=ne?NaWe=9Z-=lLCcoFC=~vHe=jchGiv4R7PCc!Af`KVma>LAXTU z7T%sI>IPFzfl-$SaR+&T*Ofp#pct7jW?v0g%>-6MGK;<631vLhm2ep;b;sTUgJt!7 zMA#)%n2(!1rVFO&AFIoc)de@+AFIp%i|SHYdSyzUooO5ndz0?^Rco(HSEK){`n2Al zj0M%?G7%mUy38}?0W&cjG}&PobWBp6e%99AnoTlE~=NC!sLO^h9S4tjGpZA%waWzcA1hhITjtv;;AE1!pa%YdYo zY|=Uw4q(UoJi@1+$HBoLQrif2;&~Vpyg^N>m1E*$2D9@tbecTF8l-zn*`tDgfKGLrZ3J<*k!}-IT z)xLwQ7@ds!LeZ$wmFF|ttFVkl`!t75y$V6<-K4#L@hX}uyiqgR+SkBu+Pp#KMw&(y zd$+Owehu=4p;y($Wl45R`Xsed;Li`l2IEA9O{)yAZKL=575_#&=f8o~9~z5&HT z|5{B{=ZGuL=y9<2RX{PJk76^wY zhq2*GqrVqb8mc#;g!ZpirDR*rLCFDu9aLJkNa^Hb(crhhl>FbTOwa9lryd%d1M zKZmt9K5k7z)AL?ZzS7!_PGG;OO!H-N zbkgr)V9S@Oj6L4$S%aLPQqm+%@}J^p!QMD57y4!F`erBd*3_s1c4^G)Zim(0;a;hbHqX1Dajv*Dk2rK#}T7OHNu1>Mkl05B4v#P5{*$A zo61?9o>U+;yJBGqX`W9)ow--W&T>}A`V_0U73sxOu&6?%m$F&?ffN$TG@MGo97x7B z@jpad))|DFIdZlPYLpT=FB$h|q#GMYjNzxCLKznF*!ANe%#mbHD?4kFV{xCTF zTfu#J9LfvidYN3AcY71DXc7l9McX?jQ1*?VVSC&$0j=|7*ECP+Gsh=#KThtc03OB0 zlQA(edxFz7i9UaM0xM2@x0W`ZM0CtH>iD)p8 z2(injQM}R}DBi_MD6s@~Y8cBCfpCCzUqX1-6q31l2YS6{2c83Mo6_9X8&8fYG>X}m zJ5c;5@Mhk$Ln{M~@uV{96|v=0P*tg#KA!Z3{MJOO(Ko3PS`kku6WHY)i2TSDRN7Qz zCHP09v4|82#uH*JodSsoO-ei*5o7+SFU~4=!$A>Xfeq6)vbfIFp>)S|05E(IeBd@l3sLPIXvg(@Zznu;UwP-cTT zivV`05ry~%M9%ZaH6AOK`C)=p>;_k(s#f}wbu17IL?V)MvTyZ;8pW6vJsojhOicKg z{UT=fKko(uJFpwd>$K%*XvjJ$C9DB|vQb{L{Ys4X$8pEDPh*vNZ8r=Ha^YAShC>#S z>#)O6_UzwJqsu3!VQqm@KMkf5g2%0M%_(z^=}uD#o~34)rlT@W?a*3&ih2MicWsSZh zM(${1BI*|>WQ5ePonsK0uOcYBL(`y)rv-S}n}g$RyWP>mEXr=7Ov(QX DDr3W; delta 3308 zcmYLLdvFuS8Nd5Ze(c@pR>G2uV~A~R2w?C_Hh##)RtyD7f^EPy7{5*@Sr(QhKO_UC z1?&kVw4q>9ylIn`GHudKr%W*>7I_dzCzDK`1AmZYI;Di^ObAWd@H7}GbWhR}&#XtQ z-?!iXzTfv)?K&gx`bs*GZqDa8PG3268R)7XU1aC;$2$+> z;?=bNM4?D&8`BHJNlg#V;ab%lN6t2Ts1? zah;!qU&rG{f*oV!Cp-+c=#fIZp75Z@8Bc*y!>EtWN!_>l;_0>{;P*|lV^BC zjgTdsJd^~rH12>DIVr(YhR3pSF^BKrTljl?8c*P(_z3o44L9R+B+~@x=8y49_@_9G z>+vf5BzE9pdidW8Db9dTk`i=z@wbtrOqj-N=tln&=vqwIBD(Y#_0M^9<>ETL64&4| zT!;PEe+m|0)-iFO<)e_msDklfRKK%3sJI%BSVm<=5mF~lX(sk(@ z=^xT(()-dG>7;Z_`lIw4>7cY1e~!PFc1l~N9!ZrpN_EncQi(KQvPek0FWwL@uU*dDa#1(?G{ID8l{Q_d)E<2S}V#F7bB{LtuVj_7?N znVY@Z=}fkgBm1Egwa-JGHM)KI7;He?Trs|Va16Gg?i|FWM$zGMP|=2LX8heatW~%< zBA240+_0bp$oLINA)Uvmoi__{S9MgBFbj3fV(i~O0Ufw6J-+2sO$!Z?tmo+^z9z^f?$=?VvFkAukETI$k%mG&m>e*W z|IDTS&c6ZH%@rd3^C*qZ2&^%4R>aMY2D9B24EAV#VwQxs*DoWm5j9zu{*IGSXWE{^0@~v8 z2elq@X+D&Z#y2S6!?KWK88*xijNt}FrUV_bRjh;f0G(CVHS&BCFi_Uv@M%xoG-pf)5a-3)Q1{& zlm2X~(b+el0|kunl#}wez$>kknOatIxD_0hJk{?XUL&#NBN?jK7dvix8;)XCj2`E$ zT$SqfhGWEjzlak1wvDC}or0NY6N>vJxu;+~>P(Jm#aqeVQ&h>HBrzqwI|YNrmP8hX zW2lJitcMKpr#wg_{dtfo5Az9#%WyjBb=$~?@4zCGmrpf3p9f3OMg!uo4w2l`u-4om zL@S+II$|9=)&O%-3W_od$}36=D(q!>g(W4$_TnUR$|$6db;3xBx|%lRa92>&{U9qh zvTl%EvV--|X+b2-Dx@9ym+%M06JQF}4GhK%i1ivQOY!zbxzQe6G2rQ=uLf<8OFyS+ z*mVtRk;l(;{`4BOn_7BU!ITWB11@j$BJ!1sY8JW<7P)bAG#cv6J`ltHd>xkH79T5u z(muauE4g%&hM#x?O3hj~>+I!yes!CNp3<>tjh4r=H)sp0UY3QUH=qTzcd@PcCNx`B zPkc^r<-tBrU?3O{F&7yg>SB^QOL4ndgihV4*W>o--Dx9JH=zpoG!|jiEpTDY^@xye zthZ$V5fM;vS9T<#(E4wN8%eTR%^tLj$9RXK>9O;H~viuIrH+FAgKw}^} zY-}XcN7HcP4&*4!Eex~=)Iq{e({cIm4%kueM#N?71A;lh{8o+v8l`AIps?A_kJ)K9?(4T4!^8heOUxn)L9$t6l5j0i|F`r zKgrm)*r5&ioh&o&+QEZbYT_D_f?`;Q8mr^OjqEFiI-_H0{1xMdx5x8zz)thDe>crR zb_r~e`l}eFW^!pa)RHSDFc&vfGCA_S1HpbWUP85O*+UOCErA(WbugtzmV3Ks3+eDL zm%uyb?h2ODIQ`t5xKe1q&+NyF=Q(dW4|@X;d0oIwiL76lvO|<*6hsnsxIGPxBjG9$)^?I zGzE%Sn%cU2ewu(NnNp1I)(O0#=n(6r^QsC9|G~ z9FkE>R!xfG%D23@hq +
+

+ {% trans "Template type allows you to create dynamic URLs with parameters. Use {query, default=value} syntax in the URL." %} +

+

+ {% trans "Example: https://google.com/search?q={query, default=hello}" %} +

+
diff --git a/links/urls.py b/links/urls.py index 44bd7d4..e7d553f 100644 --- a/links/urls.py +++ b/links/urls.py @@ -13,6 +13,7 @@ urlpatterns = [ path('link//edit/', views.LinkUpdateView.as_view(), name='link_update'), path('tools/', views.ToolsView.as_view(), name='tools'), path('/', views.redirect_to_original, name='redirect_to_original'), + path('//', views.redirect_to_original, name='redirect_to_original_with_param'), path('export/', views.export_links, name='export_links'), # New route for link detail view by alias diff --git a/links/views.py b/links/views.py index 4376f45..b115ce2 100644 --- a/links/views.py +++ b/links/views.py @@ -18,6 +18,7 @@ import random from django.utils.text import slugify import markdown import logging +import re logger = logging.getLogger(__name__) @@ -178,16 +179,40 @@ def delete_selected(request): Link.objects.filter(id__in=selected_ids).delete() return redirect('link_list') -def redirect_to_original(request, alias): - # 将别名转换为小写并移除特殊字符 +def redirect_to_original(request, alias, param=None): processed_alias = ''.join(e for e in alias.lower() if e.isalnum()) try: link = Link.objects.get(alias=processed_alias) link.click_count = F('click_count') + 1 link.save() - ClickLog.objects.create(link=link) # 记录点击 - return redirect(link.original_url) + ClickLog.objects.create(link=link) + + try: + # 如果是模板 URL 并且提供了参数 + if param: + # 从 URL 中提取参数名 + pattern = r'\{([^{}]*)\}' + matches = re.finditer(pattern, link.original_url) + for match in matches: + param_str = match.group(1) + if ',' in param_str: + param_name = param_str.split(',')[0].strip() + else: + param_name = param_str.strip() + # 使用提供的参数值 + url = link.get_processed_url(**{param_name: param}) + return redirect(url) + + # 如果没有提供参数,使用默认值 + url = link.get_processed_url() + if url is None: + url = link.original_url + return redirect(url) + except ValueError as e: + messages.error(request, str(e)) + return redirect('link_list') + except Link.DoesNotExist: messages.warning(request, _("The alias '{}' doesn't exist. Do you want to create a new one?").format(processed_alias)) return redirect(reverse('link_create') + f'?alias={processed_alias}') diff --git a/locale/zh_Hans/LC_MESSAGES/django.mo b/locale/zh_Hans/LC_MESSAGES/django.mo index 19ebde9b441ee175c625e559041e5642355f641a..5ef2d4068c8743bde831b8fd23e56182087fbb47 100644 GIT binary patch delta 1253 zcmYk)NlX+$6oBCxW)uP0gS$?dFFKA!D=k}DVRci zb%~o?0@v^ej^a{uFiQ$1p$}J}ETjvU;BAz`4^RpZVJdz^nLml=F^Y*;#3Y$tjFeEN zm`Z(B&W$9xj4V=hq71x+vSoKM2m5dje#Y&%nAPpaTrA+TRiY$*&LmmDAWDA>5952> zgt2U|#Ivzk2Ig@i9?~Aw7NdNU3n&vBv`sqRuKPXOhuWto`CjV&2yUf6f%2jeT#0iD z?7s|5=fdKYf$WybLW$>bIgW==w(ur;@G+jmPspj(^HB7ljRhzR9zc14VU)rl?N@C$ zk^N`a)NcYM7{i|{?xA&q${o$726_Nz5C9ZV* E1*ZzamH+?% delta 1023 zcmYk)Pe@cz6vy#1yk||LGddIf_0OOMO;asiK2i!zA8BHNK5%{0Ou77PWsC)A$LsZyqUS3#fHVn4`ai zRO3*#0kyCdbyXc$!V>o23EYl%u@9%PpV##pRk({yYTSp4_v0W|aVtiie}h^#jWs>Y zdE&D>k9rlqP=x~ChfJaJS&!S@g4>Cz*W>X%+(vu|byJsc6ON(QJ+9~e>0y&3xD9*l zKTP8g@f_+3TUcGjB3AJjs=#yi6<#2Ik2+|XO*&`=)p*c7>Yhiv+Ux1zzlCm*(8h81 z0jj_=q^8B5pYiyE`w{gKe#2g@ Date: Sat, 2 Nov 2024 22:31:13 +1100 Subject: [PATCH 2/6] Add dump db function --- links/templates/links/tools.html | 104 ++++++++++++++++++--------- links/urls.py | 4 +- links/views.py | 39 ++++++++++ locale/zh_Hans/LC_MESSAGES/django.po | 17 ++++- new_theme/static/css/dist/styles.css | 70 ++++++++++++++++++ 5 files changed, 199 insertions(+), 35 deletions(-) diff --git a/links/templates/links/tools.html b/links/templates/links/tools.html index c3ea0a3..897150f 100644 --- a/links/templates/links/tools.html +++ b/links/templates/links/tools.html @@ -7,28 +7,61 @@

{% trans "Tools" %}

-
-

{% trans "Export Aliases" %}

- - {% csrf_token %} - - - -

{% trans "Import Aliases" %}

-
-

{% trans "Import aliases from a JSON file. The file should contain a list of objects with the following fields:" %}

-
    -
  • {% trans "alias (required): The short alias for the URL" %}
  • -
  • {% trans "original_url (required): The original URL" %}
  • -
  • {% trans "created_at (optional): Creation timestamp (ISO format)" %}
  • -
  • {% trans "updated_at (optional): Last update timestamp (ISO format)" %}
  • -
+
+ +
+
+

{% trans "Export Aliases" %}

+
+
+
+ {% csrf_token %} + +
+
-
-

{% trans "Example JSON file content:" %}

-
+
+      
+      
+
+

{% trans "Database Export" %}

+
+
+

{% trans "Export the entire database as a compressed file." %}

+ + + + + {% trans "Export Database" %} + +
+
+ + +
+
+

{% trans "Import Aliases" %}

+
+
+
+

{% trans "Import aliases from a JSON file. The file should contain a list of objects with the following fields:" %}

+
    +
  • {% trans "alias (required): The short alias for the URL" %}
  • +
  • {% trans "original_url (required): The original URL" %}
  • +
  • {% trans "created_at (optional): Creation timestamp (ISO format)" %}
  • +
  • {% trans "updated_at (optional): Last update timestamp (ISO format)" %}
  • +
+
+ +
+

{% trans "Example JSON file content:" %}

+
 [
   {
     "alias": "example",
@@ -41,18 +74,25 @@
     "original_url": "https://www.google.com"
   }
 ]
-        
-
-
- {% csrf_token %} -
- +
+
+ + + {% csrf_token %} +
+ +
+ +
- - - {% trans "Export Links" %} +
diff --git a/links/urls.py b/links/urls.py index e7d553f..02dd6a7 100644 --- a/links/urls.py +++ b/links/urls.py @@ -12,10 +12,10 @@ urlpatterns = [ path('link//', views.LinkDetailView.as_view(), name='link_detail'), path('link//edit/', views.LinkUpdateView.as_view(), name='link_update'), path('tools/', views.ToolsView.as_view(), name='tools'), + path('tools/export-database/', views.export_database, name='export_database'), + path('/', views.redirect_to_original, name='redirect_to_original'), path('//', views.redirect_to_original, name='redirect_to_original_with_param'), path('export/', views.export_links, name='export_links'), - - # New route for link detail view by alias path('alias//', views.LinkDetailView.as_view(), name='link_detail_by_alias'), ] diff --git a/links/views.py b/links/views.py index b115ce2..bff9b92 100644 --- a/links/views.py +++ b/links/views.py @@ -19,6 +19,14 @@ from django.utils.text import slugify import markdown import logging import re +import os +import tarfile +import tempfile +from datetime import datetime +from django.conf import settings +from django.http import FileResponse +from wsgiref.util import FileWrapper +from django.contrib.auth.decorators import user_passes_test logger = logging.getLogger(__name__) @@ -390,3 +398,34 @@ class CustomLinkView(DetailView): # Render markdown text as HTML context['rendered_text'] = markdown.markdown(self.object.text) return context + +def export_database(request): + """Export the entire SQLite database as a tarball""" + try: + # 获取数据库文件路径 + db_path = settings.DATABASES['default']['NAME'] + + # 创建临时目录 + with tempfile.TemporaryDirectory() as temp_dir: + # 创建带时间戳的文件名 + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + tar_filename = f'database_backup_{timestamp}.tar.gz' + tar_filepath = os.path.join(temp_dir, tar_filename) + + # 创建tar文件 + with tarfile.open(tar_filepath, 'w:gz') as tar: + # 添加数据库文件到tar + tar.add(db_path, arcname=os.path.basename(db_path)) + + # 打开文件准备下载 + wrapper = FileWrapper(open(tar_filepath, 'rb')) + response = FileResponse(wrapper, content_type='application/x-gzip') + response['Content-Disposition'] = f'attachment; filename="{tar_filename}"' + response['Content-Length'] = os.path.getsize(tar_filepath) + + return response + + except Exception as e: + logger.error(f"Database export failed: {str(e)}") + messages.error(request, _("Failed to export database: {}").format(str(e))) + return redirect('tools') diff --git a/locale/zh_Hans/LC_MESSAGES/django.po b/locale/zh_Hans/LC_MESSAGES/django.po index cec6387..0d99c24 100644 --- a/locale/zh_Hans/LC_MESSAGES/django.po +++ b/locale/zh_Hans/LC_MESSAGES/django.po @@ -1856,7 +1856,7 @@ msgstr "无效的JSON格式。预期是对象列表。" #: links/views.py:282 msgid "" "Invalid item in JSON. Each item must be an object with an 'alias' field." -msgstr "JSON中存在无效项。每个项���必须是包含'alias'字段的对象。" +msgstr "JSON中存在无效项。每个项必须是包含'alias'字段的对象。" #: links/views.py:295 #, fuzzy, python-brace-format @@ -1913,3 +1913,18 @@ msgstr "描述" msgid "Please enter a valid URL. Template parameters are allowed in the format {param_name,default=value}." msgstr "请输入有效的URL。允许使用模板参数,格式为 {参数名,default=默认值}。" + +msgid "Database Export" +msgstr "数据库导出" + +msgid "Export the entire database as a compressed file. This feature is only available to administrators." +msgstr "将整个数据库导出为压缩文件。此功能仅管理员可用。" + +msgid "Export Database" +msgstr "导出数据库" + +msgid "Only administrators can export the database." +msgstr "只有管理员可以导出数据库。" + +msgid "Failed to export database: {}" +msgstr "导出数据库失败:{}" diff --git a/new_theme/static/css/dist/styles.css b/new_theme/static/css/dist/styles.css index cfeedf9..876544f 100644 --- a/new_theme/static/css/dist/styles.css +++ b/new_theme/static/css/dist/styles.css @@ -949,6 +949,12 @@ video { margin-bottom: calc(1.5rem * var(--tw-space-y-reverse)); } +.space-y-8 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(2rem * var(--tw-space-y-reverse)); +} + .divide-y > :not([hidden]) ~ :not([hidden]) { --tw-divide-y-reverse: 0; border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); @@ -1132,6 +1138,11 @@ video { background-color: rgb(254 249 195 / var(--tw-bg-opacity)); } +.bg-purple-500 { + --tw-bg-opacity: 1; + background-color: rgb(168 85 247 / var(--tw-bg-opacity)); +} + .p-2 { padding: 0.5rem; } @@ -1144,6 +1155,10 @@ video { padding: 1.5rem; } +.p-3 { + padding: 0.75rem; +} + .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; @@ -1444,6 +1459,47 @@ video { transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } +.file\:mr-4::file-selector-button { + margin-right: 1rem; +} + +.file\:rounded-md::file-selector-button { + border-radius: 0.375rem; +} + +.file\:border-0::file-selector-button { + border-width: 0px; +} + +.file\:bg-blue-50::file-selector-button { + --tw-bg-opacity: 1; + background-color: rgb(239 246 255 / var(--tw-bg-opacity)); +} + +.file\:px-4::file-selector-button { + padding-left: 1rem; + padding-right: 1rem; +} + +.file\:py-2::file-selector-button { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.file\:text-sm::file-selector-button { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.file\:font-medium::file-selector-button { + font-weight: 500; +} + +.file\:text-blue-700::file-selector-button { + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity)); +} + .hover\:border-blue-700:hover { --tw-border-opacity: 1; border-color: rgb(29 78 216 / var(--tw-border-opacity)); @@ -1499,6 +1555,11 @@ video { background-color: rgb(185 28 28 / var(--tw-bg-opacity)); } +.hover\:bg-purple-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(147 51 234 / var(--tw-bg-opacity)); +} + .hover\:text-blue-700:hover { --tw-text-opacity: 1; color: rgb(29 78 216 / var(--tw-text-opacity)); @@ -1548,6 +1609,11 @@ video { text-decoration-line: underline; } +.hover\:file\:bg-blue-100::file-selector-button:hover { + --tw-bg-opacity: 1; + background-color: rgb(219 234 254 / var(--tw-bg-opacity)); +} + .focus\:border-blue-300:focus { --tw-border-opacity: 1; border-color: rgb(147 197 253 / var(--tw-border-opacity)); @@ -1736,6 +1802,10 @@ video { grid-template-columns: repeat(5, minmax(0, 1fr)); } + .lg\:p-8 { + padding: 2rem; + } + .lg\:px-8 { padding-left: 2rem; padding-right: 2rem; From 3509dd9d9d906e63585a84a9617f3b3a889dd34f Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Sat, 2 Nov 2024 22:46:08 +1100 Subject: [PATCH 3/6] Update style --- links/templates/links/search.html | 210 +++++++++++++++++---------- new_theme/static/css/dist/styles.css | 129 ++++++++++++++++ 2 files changed, 259 insertions(+), 80 deletions(-) diff --git a/links/templates/links/search.html b/links/templates/links/search.html index 7cc4a1b..a3d08fa 100644 --- a/links/templates/links/search.html +++ b/links/templates/links/search.html @@ -3,93 +3,143 @@ {% block content %}
-

{% trans "Advanced Search" %}

+ +
+

{% trans "Advanced Search" %}

+

{% trans "Search through all links by alias or URL" %}

+
-
-
-
- -
- -
-
- - {% if query %} -

{% trans "Search results for" %}: {{ query }}

- - {% if links %} -
- + +
+

{% trans "Try searching by alias, URL, or any related keywords" %}

+
+ + + + + + {% if query %} +
+
+

+ {% trans "Search results for" %}: {{ query }} +

- {% if is_paginated %} - - {% endif %} + {% if links %} + - {% else %} -

{% trans "No results found." %}

- {% endif %} + {% if is_paginated %} + + {% endif %} + {% else %} +
+ + + +

{% trans "No results found" %}

+

{% trans "Try adjusting your search terms." %}

+
+ {% endif %} +
{% else %} -

{% trans "Enter a search query to find links." %}

+
+
+ + + +

{% trans "Start searching" %}

+

{% trans "Enter a search query to find links. You can search by alias, URL, or any related keywords." %}

+
+
{% endif %} {% endblock %} diff --git a/new_theme/static/css/dist/styles.css b/new_theme/static/css/dist/styles.css index 876544f..501797d 100644 --- a/new_theme/static/css/dist/styles.css +++ b/new_theme/static/css/dist/styles.css @@ -746,6 +746,14 @@ video { margin-top: 2rem; } +.mb-1 { + margin-bottom: 0.25rem; +} + +.-ml-px { + margin-left: -1px; +} + .block { display: block; } @@ -786,6 +794,14 @@ video { height: 100vh; } +.h-12 { + height: 3rem; +} + +.h-16 { + height: 4rem; +} + .w-4 { width: 1rem; } @@ -802,10 +818,22 @@ video { width: 100%; } +.w-12 { + width: 3rem; +} + +.w-16 { + width: 4rem; +} + .min-w-full { min-width: 100%; } +.min-w-0 { + min-width: 0px; +} + .max-w-4xl { max-width: 56rem; } @@ -834,6 +862,14 @@ video { max-width: 20rem; } +.max-w-3xl { + max-width: 48rem; +} + +.max-w-md { + max-width: 28rem; +} + .flex-1 { flex: 1 1 0%; } @@ -884,6 +920,10 @@ video { align-items: flex-start; } +.items-end { + align-items: flex-end; +} + .items-center { align-items: center; } @@ -955,6 +995,12 @@ video { margin-bottom: calc(2rem * var(--tw-space-y-reverse)); } +.space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + .divide-y > :not([hidden]) ~ :not([hidden]) { --tw-divide-y-reverse: 0; border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); @@ -1000,6 +1046,16 @@ video { border-radius: 0.375rem; } +.rounded-l-md { + border-top-left-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; +} + +.rounded-r-md { + border-top-right-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; +} + .border { border-width: 1px; } @@ -1143,6 +1199,16 @@ video { background-color: rgb(168 85 247 / var(--tw-bg-opacity)); } +.bg-green-600 { + --tw-bg-opacity: 1; + background-color: rgb(22 163 74 / var(--tw-bg-opacity)); +} + +.bg-purple-600 { + --tw-bg-opacity: 1; + background-color: rgb(147 51 234 / var(--tw-bg-opacity)); +} + .p-2 { padding: 0.5rem; } @@ -1219,10 +1285,23 @@ video { padding-bottom: 2rem; } +.py-12 { + padding-top: 3rem; + padding-bottom: 3rem; +} + .pr-3 { padding-right: 0.75rem; } +.pl-5 { + padding-left: 1.25rem; +} + +.pr-10 { + padding-right: 2.5rem; +} + .text-left { text-align: left; } @@ -1270,6 +1349,11 @@ video { line-height: 1rem; } +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + .font-bold { font-weight: 700; } @@ -1402,6 +1486,11 @@ video { color: rgb(161 98 7 / var(--tw-text-opacity)); } +.text-gray-300 { + --tw-text-opacity: 1; + color: rgb(209 213 219 / var(--tw-text-opacity)); +} + .underline { text-decoration-line: underline; } @@ -1451,10 +1540,20 @@ video { transition-duration: 150ms; } +.transition-colors { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + .duration-150 { transition-duration: 150ms; } +.duration-200 { + transition-duration: 200ms; +} + .ease-in-out { transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } @@ -1560,6 +1659,11 @@ video { background-color: rgb(147 51 234 / var(--tw-bg-opacity)); } +.hover\:bg-purple-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(126 34 206 / var(--tw-bg-opacity)); +} + .hover\:text-blue-700:hover { --tw-text-opacity: 1; color: rgb(29 78 216 / var(--tw-text-opacity)); @@ -1650,6 +1754,12 @@ video { box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } +.focus\:ring-1:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + .focus\:ring-blue-200:focus { --tw-ring-opacity: 1; --tw-ring-color: rgb(191 219 254 / var(--tw-ring-opacity)); @@ -1675,6 +1785,16 @@ video { --tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity)); } +.focus\:ring-green-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(34 197 94 / var(--tw-ring-opacity)); +} + +.focus\:ring-purple-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(168 85 247 / var(--tw-ring-opacity)); +} + .focus\:ring-opacity-50:focus { --tw-ring-opacity: 0.5; } @@ -1766,6 +1886,10 @@ video { padding: 1.5rem; } + .sm\:p-12 { + padding: 3rem; + } + .sm\:px-6 { padding-left: 1.5rem; padding-right: 1.5rem; @@ -1785,6 +1909,11 @@ video { font-size: 1rem; line-height: 1.5rem; } + + .sm\:text-sm { + font-size: 0.875rem; + line-height: 1.25rem; + } } @media (min-width: 768px) { From 5e231805c3ae2147f1beebba30c6ee3c2e3417df Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Sun, 3 Nov 2024 09:02:39 +1100 Subject: [PATCH 4/6] Updated the logic to handle template exist at any positions within the URL --- data/db.sqlite3 | Bin 204800 -> 204800 bytes links/models.py | 38 ++++++++++++++++++++++++++++---------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/data/db.sqlite3 b/data/db.sqlite3 index a6f7eda27cd66f2dd324d23abf4d46e945442347..6d0342bd19b5bb27f2c781990b3bd3939231bf22 100644 GIT binary patch delta 919 zcmZoTz|(MmXM!}N|3n#QM*ocoAM_a6CkN`QFzPm!>2EL7XY9Mcti$Com7OV&gKITc z7gr{i$7V%^7o7F-x-1Nij7A1VCc1`(x&}rHMut|V7FNc_dS)iZ=Ef%Z@;X=)m|Gbe z>RDJAm|L3X%WGp%U}R-ru4imuW?^8OFRz6~fuWVLiJrL;yM>97alV4)^bcH2qT3&H zF)6Y#>+rpu&Mv@Y%E9-a?&N6ck(O>lf=~mFAV{B`50_ zrzRF9XV@3oRu`707FFsfq@<=Lmgbb$=B1VwE0pA?Bvwwpkie8!PnOnNwc^CIREQn6 z25Kc4sky1P#hJMUIjI;SZfLG&YG!0=Xqw*?%?5OEV3Q@1gQw3+V3L=_??zLy+z9rb z8k%!}X~0a++|ty{(jvbpgB9qU;HE%F#_3ZNm==Qw4FXP)#xT#&LeJ9D(AdH}fBU6* zOh$}C?EF_5_urF_Y-j{mcT2pkxe5rRL^l29~Too{gdirH#4> fr2 1: + # 处理默认值部分 + default_part = param_parts[1].strip() + if default_part.startswith('default='): + default_value = default_part[8:].strip() # 移除 'default=' 前缀 + # 移除引号(如果存在) + if (default_value.startswith('"') and default_value.endswith('"')) or \ + (default_value.startswith("'") and default_value.endswith("'")): + default_value = default_value[1:-1] + + params[param_name] = { + 'default': default_value, + 'full_match': param_full + } + + # 替换所有参数 + for param_name, param_info in params.items(): + value = kwargs.get(param_name, param_info['default']) if value is None: raise ValueError(f"No value provided for parameter {param_name}") - processed_url = processed_url.replace(f"{{{param_str}}}", str(value)) + # 使用完整的匹配模式进行替换 + processed_url = processed_url.replace(param_info['full_match'], str(value)) return processed_url From 9f61ff1dabb916ad35563fd6feb94957fbdde0f9 Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Sun, 3 Nov 2024 09:13:27 +1100 Subject: [PATCH 5/6] Show the template urls with a different typeOC --- links/templates/links/link_detail.html | 6 ++++- links/templates/links/link_list.html | 7 +++++- new_theme/static/css/dist/styles.css | 34 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/links/templates/links/link_detail.html b/links/templates/links/link_detail.html index f60f7a0..273e2ef 100644 --- a/links/templates/links/link_detail.html +++ b/links/templates/links/link_detail.html @@ -8,7 +8,11 @@

{% trans "Link Details" %} - {% if link.link_type == 'LINK' %} + {% if '{' in link.original_url and '}' in link.original_url %} + + {% trans "Template" %} + + {% elif link.link_type == 'LINK' %} {% trans "Link" %} diff --git a/links/templates/links/link_list.html b/links/templates/links/link_list.html index a373c5e..a6bba24 100644 --- a/links/templates/links/link_list.html +++ b/links/templates/links/link_list.html @@ -105,10 +105,15 @@ - {% if link.link_type == 'LINK' %} + {% if '{' in link.original_url and '}' in link.original_url %} + + Template + {% elif link.link_type == 'LINK' %} + Link {% else %} + Custom {% endif %} diff --git a/new_theme/static/css/dist/styles.css b/new_theme/static/css/dist/styles.css index 501797d..04d2eba 100644 --- a/new_theme/static/css/dist/styles.css +++ b/new_theme/static/css/dist/styles.css @@ -1209,6 +1209,11 @@ video { background-color: rgb(147 51 234 / var(--tw-bg-opacity)); } +.bg-purple-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 232 255 / var(--tw-bg-opacity)); +} + .p-2 { padding: 0.5rem; } @@ -1491,6 +1496,21 @@ video { color: rgb(209 213 219 / var(--tw-text-opacity)); } +.text-purple-500 { + --tw-text-opacity: 1; + color: rgb(168 85 247 / var(--tw-text-opacity)); +} + +.text-purple-600 { + --tw-text-opacity: 1; + color: rgb(147 51 234 / var(--tw-text-opacity)); +} + +.text-purple-800 { + --tw-text-opacity: 1; + color: rgb(107 33 168 / var(--tw-text-opacity)); +} + .underline { text-decoration-line: underline; } @@ -1523,6 +1543,20 @@ video { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } +.ring-1 { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.ring-inset { + --tw-ring-inset: inset; +} + +.ring-blue-700\/10 { + --tw-ring-color: rgb(29 78 216 / 0.1); +} + .blur { --tw-blur: blur(8px); filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); From e8a9bd1aa5bf267ad0cc10e4fbbdcab263878f89 Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Sun, 3 Nov 2024 10:11:25 +1100 Subject: [PATCH 6/6] Add help page --- data/db.sqlite3 | Bin 204800 -> 204800 bytes links/patches.py | 4 + links/templates/links/help.html | 141 +++++++++++++ links/templates/links/link_detail.html | 2 +- links/templates/links/link_list.html | 6 +- links/templatetags/markdown_extras.py | 8 + links/urls.py | 9 +- links/views.py | 3 + new_theme/static/css/dist/styles.css | 270 +++++++++---------------- templates/base.html | 1 + url_manager/settings.py | 2 + 11 files changed, 264 insertions(+), 182 deletions(-) create mode 100644 links/patches.py create mode 100644 links/templates/links/help.html create mode 100644 links/templatetags/markdown_extras.py diff --git a/data/db.sqlite3 b/data/db.sqlite3 index 6d0342bd19b5bb27f2c781990b3bd3939231bf22..6d07b08edf22685f529fc05033d732dd0592ce44 100644 GIT binary patch delta 829 zcmZva%}Z2K7{<@J*YSSL%zZ~j8|Xtv2_n#Q&)0S4UU4zCQ4+Cb;KD&I`yj`h85l@( z2wF-Jav`DqgCZoZizo$mE=)rr+Vrt-)zGF@NL^YXv(#OdBKbr-;GV(#J0ZJ z<+Pw2=9J4m#`-8vbMA51V}dY8a90$%WD_9kaY=+jT`me~5dz8yhpb1lgi9u92Ki~; zoDhUA3M#WQL|790JdS(dTXu2eJH^g48tWD0BL=JJ~$0Y>yx!-ZChIq zgUDKUddPAjD#nGzD_d0sBVROxH-t5W)@8*$!e>eFqf<%Ewd>{xf=w8OgtZsH5i`s~ zb0lJK2@|xyAC@De#0;L3ZG#gcAy3aYi$z&wALf( z!Tw97cPt7ss2kM~szK#-OikhB@djze;o8XX=oSCjsxlTl#Ok+r8X?q%lDQo2Kw*Gz z+-gf9+W3gatJ=K3ds|8Q%b-R(PV`T@GAmmA7oeu<(M~mw7W@HqE1o#&ZKHJ}>$+0& XKb%uP9$WtF0dxBMBf+~)HHZHKCMUwC delta 290 zcmV+-0p0$9zzl%E43HZEPmvr$0Z*}D@Gb!dlTj}u0WE`eFSm9t0gliEEDA`K2Lf5M zAt2}pw@3;CyafX+4(*o*5CT0M56cgm4}1@G4^R(D4?+(+4>S)g4;l{>4-5|lvmqdj z4!7+d0vB--2MYZF55W&k4-gL04QC7q3jMPoAm<9V{h9&?1ri4j)c_Cs5A_e>58Dsb z5AF}=vk_pw54Y8?0xVDq0000X0PX+)?hk0S3tjx6G1@%6#xK%DRY<6YXTRSpAQ2Im*;;18JEKk0~42we*!zU5Cg*v o5HT@2F)%tZG%htVG%__f1DDes1J{?I-vS1gm)-(fmkS>Q_2rpWmH+?% diff --git a/links/patches.py b/links/patches.py new file mode 100644 index 0000000..e137f1c --- /dev/null +++ b/links/patches.py @@ -0,0 +1,4 @@ +import sys +from django import urls + +sys.modules['django.core.urlresolvers'] = urls diff --git a/links/templates/links/help.html b/links/templates/links/help.html new file mode 100644 index 0000000..d4ac24b --- /dev/null +++ b/links/templates/links/help.html @@ -0,0 +1,141 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load markdown_extras %} + +{% block extra_css %} + + + +{% endblock %} + +{% block content %} +
+
+ {% filter markdown %} +# Frequently Asked Questions (FAQ) + +## Link Types + +GoLinks supports three types of links: Normal Links, Template Links, and Custom Pages. Each type serves different purposes and has its own use cases. + +### 1. Normal Links + +Normal links are the simplest type - they provide direct redirection to a target URL. + +**Example:** +- Original URL: `https://github.com/microsoft/vscode` +- Alias: `vscode` +- Access: `http://go/vscode` → Redirects to VS Code GitHub repository + +**Use Cases:** +- Quick access to frequently visited websites +- Shortening long URLs +- Creating memorable aliases for complex URLs + +### 2. Template Links + +Template links allow you to create dynamic URLs that can generate the final URL based on parameters. This is particularly useful for search links, API calls, or any URL that needs dynamic parameters. + +**Basic Syntax:** +Use the syntax `{parameter_name, default=default_value}` to define variables in your URL: `https://google.com?q={query, default=hello}`, and set the alias to `google`. + +**Examples:** +- Use the syntax `{query, default=default_value}` to define variables in your URL: `https://google.com?q={query, default=hello}`, and set the alias to `google`. + + Then you can access the link by `http://go/google/`, and the URL will be `https://google.com?q=`. + + When you access the url with `http://go/google/`, the URL will be `https://google.com?q=hello`, which will leverage the `default` value. + +### 3. Custom Pages + +Custom pages allow you to create markdown-based web pages that can serve as documentation, link collections, or any other content you need. + +**Key Features:** +- Write content in Markdown format +- Support for headings, lists, code blocks, and links +- Can include multiple sections and navigation +- Perfect for creating documentation or resource collections + +**Example Use Cases:** + Check http://go.junv.cc/custom/today/ + {% endfilter %} +
+
+{% endblock %} diff --git a/links/templates/links/link_detail.html b/links/templates/links/link_detail.html index 273e2ef..f1eaec3 100644 --- a/links/templates/links/link_detail.html +++ b/links/templates/links/link_detail.html @@ -23,7 +23,7 @@ {% endif %}

- + diff --git a/links/templates/links/link_list.html b/links/templates/links/link_list.html index a6bba24..05a4c06 100644 --- a/links/templates/links/link_list.html +++ b/links/templates/links/link_list.html @@ -119,7 +119,11 @@ {{ link.click_count }} diff --git a/links/templatetags/markdown_extras.py b/links/templatetags/markdown_extras.py new file mode 100644 index 0000000..a4186bf --- /dev/null +++ b/links/templatetags/markdown_extras.py @@ -0,0 +1,8 @@ +from django import template +import markdown as md + +register = template.Library() + +@register.filter +def markdown(value): + return md.markdown(value, extensions=['fenced_code', 'tables']) diff --git a/links/urls.py b/links/urls.py index 02dd6a7..af8f5fb 100644 --- a/links/urls.py +++ b/links/urls.py @@ -8,14 +8,17 @@ urlpatterns = [ path('delete//', views.LinkDeleteView.as_view(), name='link_delete'), path('delete-selected/', views.delete_selected, name='delete_selected'), path('detail//', views.LinkDetailView.as_view(), name='link_detail'), - path('search/', views.SearchView.as_view(), name='search'), + path('ui/search/', views.SearchView.as_view(), name='search'), path('link//', views.LinkDetailView.as_view(), name='link_detail'), path('link//edit/', views.LinkUpdateView.as_view(), name='link_update'), - path('tools/', views.ToolsView.as_view(), name='tools'), + path('ui/tools/', views.ToolsView.as_view(), name='tools'), path('tools/export-database/', views.export_database, name='export_database'), + path('ui/help/', views.HelpView.as_view(), name='help'), + path('export/', views.export_links, name='export_links'), + # Aliases path('/', views.redirect_to_original, name='redirect_to_original'), path('//', views.redirect_to_original, name='redirect_to_original_with_param'), - path('export/', views.export_links, name='export_links'), path('alias//', views.LinkDetailView.as_view(), name='link_detail_by_alias'), + ] diff --git a/links/views.py b/links/views.py index bff9b92..f73997b 100644 --- a/links/views.py +++ b/links/views.py @@ -429,3 +429,6 @@ def export_database(request): logger.error(f"Database export failed: {str(e)}") messages.error(request, _("Failed to export database: {}").format(str(e))) return redirect('tools') + +class HelpView(TemplateView): + template_name = 'links/help.html' diff --git a/new_theme/static/css/dist/styles.css b/new_theme/static/css/dist/styles.css index 04d2eba..ed31bb6 100644 --- a/new_theme/static/css/dist/styles.css +++ b/new_theme/static/css/dist/styles.css @@ -670,6 +670,14 @@ video { margin-right: auto; } +.-ml-px { + margin-left: -1px; +} + +.mb-1 { + margin-bottom: 0.25rem; +} + .mb-2 { margin-bottom: 0.5rem; } @@ -694,10 +702,6 @@ video { margin-left: 0.5rem; } -.ml-3 { - margin-left: 0.75rem; -} - .ml-4 { margin-left: 1rem; } @@ -746,14 +750,6 @@ video { margin-top: 2rem; } -.mb-1 { - margin-bottom: 0.25rem; -} - -.-ml-px { - margin-left: -1px; -} - .block { display: block; } @@ -782,6 +778,14 @@ video { display: none; } +.h-12 { + height: 3rem; +} + +.h-16 { + height: 4rem; +} + .h-4 { height: 1rem; } @@ -794,12 +798,12 @@ video { height: 100vh; } -.h-12 { - height: 3rem; +.w-12 { + width: 3rem; } -.h-16 { - height: 4rem; +.w-16 { + width: 4rem; } .w-4 { @@ -818,22 +822,14 @@ video { width: 100%; } -.w-12 { - width: 3rem; -} - -.w-16 { - width: 4rem; +.min-w-0 { + min-width: 0px; } .min-w-full { min-width: 100%; } -.min-w-0 { - min-width: 0px; -} - .max-w-4xl { max-width: 56rem; } @@ -850,6 +846,10 @@ video { max-width: 32rem; } +.max-w-md { + max-width: 28rem; +} + .max-w-none { max-width: none; } @@ -862,14 +862,6 @@ video { max-width: 20rem; } -.max-w-3xl { - max-width: 48rem; -} - -.max-w-md { - max-width: 28rem; -} - .flex-1 { flex: 1 1 0%; } @@ -920,10 +912,6 @@ video { align-items: flex-start; } -.items-end { - align-items: flex-end; -} - .items-center { align-items: center; } @@ -953,6 +941,12 @@ video { row-gap: 2rem; } +.space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + .space-x-3 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(0.75rem * var(--tw-space-x-reverse)); @@ -995,12 +989,6 @@ video { margin-bottom: calc(2rem * var(--tw-space-y-reverse)); } -.space-x-2 > :not([hidden]) ~ :not([hidden]) { - --tw-space-x-reverse: 0; - margin-right: calc(0.5rem * var(--tw-space-x-reverse)); - margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); -} - .divide-y > :not([hidden]) ~ :not([hidden]) { --tw-divide-y-reverse: 0; border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); @@ -1030,6 +1018,10 @@ video { white-space: nowrap; } +.break-all { + word-break: break-all; +} + .rounded { border-radius: 0.25rem; } @@ -1165,11 +1157,21 @@ video { background-color: rgb(34 197 94 / var(--tw-bg-opacity)); } +.bg-purple-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 232 255 / var(--tw-bg-opacity)); +} + .bg-purple-50 { --tw-bg-opacity: 1; background-color: rgb(250 245 255 / var(--tw-bg-opacity)); } +.bg-purple-500 { + --tw-bg-opacity: 1; + background-color: rgb(168 85 247 / var(--tw-bg-opacity)); +} + .bg-red-500 { --tw-bg-opacity: 1; background-color: rgb(239 68 68 / var(--tw-bg-opacity)); @@ -1194,30 +1196,14 @@ video { background-color: rgb(254 249 195 / var(--tw-bg-opacity)); } -.bg-purple-500 { - --tw-bg-opacity: 1; - background-color: rgb(168 85 247 / var(--tw-bg-opacity)); -} - -.bg-green-600 { - --tw-bg-opacity: 1; - background-color: rgb(22 163 74 / var(--tw-bg-opacity)); -} - -.bg-purple-600 { - --tw-bg-opacity: 1; - background-color: rgb(147 51 234 / var(--tw-bg-opacity)); -} - -.bg-purple-100 { - --tw-bg-opacity: 1; - background-color: rgb(243 232 255 / var(--tw-bg-opacity)); -} - .p-2 { padding: 0.5rem; } +.p-3 { + padding: 0.75rem; +} + .p-4 { padding: 1rem; } @@ -1226,10 +1212,6 @@ video { padding: 1.5rem; } -.p-3 { - padding: 0.75rem; -} - .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; @@ -1265,6 +1247,11 @@ video { padding-bottom: 0.25rem; } +.py-12 { + padding-top: 3rem; + padding-bottom: 3rem; +} + .py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; @@ -1290,23 +1277,10 @@ video { padding-bottom: 2rem; } -.py-12 { - padding-top: 3rem; - padding-bottom: 3rem; -} - .pr-3 { padding-right: 0.75rem; } -.pl-5 { - padding-left: 1.25rem; -} - -.pr-10 { - padding-right: 2.5rem; -} - .text-left { text-align: left; } @@ -1334,6 +1308,11 @@ video { line-height: 1; } +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + .text-lg { font-size: 1.125rem; line-height: 1.75rem; @@ -1354,11 +1333,6 @@ video { line-height: 1rem; } -.text-base { - font-size: 1rem; - line-height: 1.5rem; -} - .font-bold { font-weight: 700; } @@ -1411,6 +1385,11 @@ video { color: rgb(30 64 175 / var(--tw-text-opacity)); } +.text-gray-300 { + --tw-text-opacity: 1; + color: rgb(209 213 219 / var(--tw-text-opacity)); +} + .text-gray-400 { --tw-text-opacity: 1; color: rgb(156 163 175 / var(--tw-text-opacity)); @@ -1466,11 +1445,26 @@ video { color: rgb(79 70 229 / var(--tw-text-opacity)); } +.text-purple-500 { + --tw-text-opacity: 1; + color: rgb(168 85 247 / var(--tw-text-opacity)); +} + +.text-purple-600 { + --tw-text-opacity: 1; + color: rgb(147 51 234 / var(--tw-text-opacity)); +} + .text-purple-700 { --tw-text-opacity: 1; color: rgb(126 34 206 / var(--tw-text-opacity)); } +.text-purple-800 { + --tw-text-opacity: 1; + color: rgb(107 33 168 / var(--tw-text-opacity)); +} + .text-red-600 { --tw-text-opacity: 1; color: rgb(220 38 38 / var(--tw-text-opacity)); @@ -1491,26 +1485,6 @@ video { color: rgb(161 98 7 / var(--tw-text-opacity)); } -.text-gray-300 { - --tw-text-opacity: 1; - color: rgb(209 213 219 / var(--tw-text-opacity)); -} - -.text-purple-500 { - --tw-text-opacity: 1; - color: rgb(168 85 247 / var(--tw-text-opacity)); -} - -.text-purple-600 { - --tw-text-opacity: 1; - color: rgb(147 51 234 / var(--tw-text-opacity)); -} - -.text-purple-800 { - --tw-text-opacity: 1; - color: rgb(107 33 168 / var(--tw-text-opacity)); -} - .underline { text-decoration-line: underline; } @@ -1519,12 +1493,6 @@ video { text-decoration-line: line-through; } -.shadow { - --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - .shadow-md { --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); @@ -1543,20 +1511,6 @@ video { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } -.ring-1 { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); -} - -.ring-inset { - --tw-ring-inset: inset; -} - -.ring-blue-700\/10 { - --tw-ring-color: rgb(29 78 216 / 0.1); -} - .blur { --tw-blur: blur(8px); filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); @@ -1678,6 +1632,11 @@ video { background-color: rgb(21 128 61 / var(--tw-bg-opacity)); } +.hover\:bg-purple-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(147 51 234 / var(--tw-bg-opacity)); +} + .hover\:bg-red-600:hover { --tw-bg-opacity: 1; background-color: rgb(220 38 38 / var(--tw-bg-opacity)); @@ -1688,16 +1647,6 @@ video { background-color: rgb(185 28 28 / var(--tw-bg-opacity)); } -.hover\:bg-purple-600:hover { - --tw-bg-opacity: 1; - background-color: rgb(147 51 234 / var(--tw-bg-opacity)); -} - -.hover\:bg-purple-700:hover { - --tw-bg-opacity: 1; - background-color: rgb(126 34 206 / var(--tw-bg-opacity)); -} - .hover\:text-blue-700:hover { --tw-text-opacity: 1; color: rgb(29 78 216 / var(--tw-text-opacity)); @@ -1782,18 +1731,18 @@ video { box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } -.focus\:ring-2:focus { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); -} - .focus\:ring-1:focus { --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } +.focus\:ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + .focus\:ring-blue-200:focus { --tw-ring-opacity: 1; --tw-ring-color: rgb(191 219 254 / var(--tw-ring-opacity)); @@ -1819,16 +1768,6 @@ video { --tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity)); } -.focus\:ring-green-500:focus { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(34 197 94 / var(--tw-ring-opacity)); -} - -.focus\:ring-purple-500:focus { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(168 85 247 / var(--tw-ring-opacity)); -} - .focus\:ring-opacity-50:focus { --tw-ring-opacity: 0.5; } @@ -1846,10 +1785,6 @@ video { grid-column: span 2 / span 2; } - .sm\:mt-0 { - margin-top: 0px; - } - .sm\:block { display: block; } @@ -1900,30 +1835,20 @@ video { margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); } - .sm\:space-x-4 > :not([hidden]) ~ :not([hidden]) { - --tw-space-x-reverse: 0; - margin-right: calc(1rem * var(--tw-space-x-reverse)); - margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); - } - .sm\:space-y-0 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse))); margin-bottom: calc(0px * var(--tw-space-y-reverse)); } - .sm\:rounded-md { - border-radius: 0.375rem; + .sm\:p-12 { + padding: 3rem; } .sm\:p-6 { padding: 1.5rem; } - .sm\:p-12 { - padding: 3rem; - } - .sm\:px-6 { padding-left: 1.5rem; padding-right: 1.5rem; @@ -1943,11 +1868,6 @@ video { font-size: 1rem; line-height: 1.5rem; } - - .sm\:text-sm { - font-size: 0.875rem; - line-height: 1.25rem; - } } @media (min-width: 768px) { @@ -1965,10 +1885,6 @@ video { grid-template-columns: repeat(5, minmax(0, 1fr)); } - .lg\:p-8 { - padding: 2rem; - } - .lg\:px-8 { padding-left: 2rem; padding-right: 2rem; diff --git a/templates/base.html b/templates/base.html index a8242c3..6571431 100644 --- a/templates/base.html +++ b/templates/base.html @@ -70,6 +70,7 @@
diff --git a/url_manager/settings.py b/url_manager/settings.py index 5fa1db0..a47df01 100644 --- a/url_manager/settings.py +++ b/url_manager/settings.py @@ -1,6 +1,7 @@ import os from pathlib import Path from django.utils.translation import gettext_lazy as _ +import links.patches # 添加这一行在文件最上方 # 构建路径,如 BASE_DIR / 'subdir' BASE_DIR = Path(__file__).resolve().parent.parent @@ -17,6 +18,7 @@ INSTALLED_APPS = [ 'tailwind', 'new_theme', 'simplemde', + 'markdown', # 只需要基本的markdown包 ] ROOT_URLCONF = 'url_manager.urls'