普通视图

发现新文章,点击刷新页面。
昨天以前樵夫的小站

Anchor 专业版手册

作者 Roy
2020年8月22日 21:57

本文为Anchor专业版的使用手册。专业版需要一定的代码基础,通过阅读本篇文档你将会学到使用Anchor,在状态栏、通知中心、Widget中,添加你想看到的内容、你需要的操作的一键按钮。

正如在展示视频中看到的那样,专业版提供了完全自定义的命令和完全自定义的界面。

另外,非常感谢你对于Anchor的认可与支持!

添加一个自定义控件

路径:单击状态栏Anchor图标 -> 新增 -> 添加自定义组件。

组件可以设定四种自动运行模式,分别是:

运行模式含义
手动运行将不会自动运行,需要通过点击”开始”运行
开启视窗时运行将在点开状态栏、划出通知中心时运行
程序启动时运行将会在主程序启动时自动运行一次
固定间隔自动运行将根据间隔不断自动运行

正如你所看到的,自定义组件的程序与代码都可以自定义。

这里做一个最简单的测试,将程序更改为:

1
python

将代码更改为:

1
print("Greeting from Anchor")

你就会获得一个”结果”固定的组件,这里建议使用左侧彩蛋第二位的测试窗口,测试窗口提供了一个简单方便的测试环境。

自定义界面

自定义状态栏按钮

状态栏除了默认的启动、隐藏、设置等,还支持添加其他的按钮。

支持的按钮有以下几类,添加多个按钮以”;”(英文分号)分隔:

种类设定值返回值
目前结果值$文本一结果值
固定值文本二文本二
输入值@文本三输入值

那么以番茄钟为例,如果需要三个自定义按钮,分别为:30、目前剩余的时间、自定义时间。

那么”状态栏自定义按钮”就设定为:

1
30;$目前剩余时间;@自定义时间

这里可以添加一个自定义任务进行尝试,测试代码使用/bin/bash的情况下可以使用:

1
echo $1

建议将结果拖入状态栏内容,这样可以方便的在状态栏看到按钮点击的结果。

自定义视窗界面

视窗界面将根据程序运行的结果自动生成,目前支持四种自定义视窗以及四种视窗的任意组合。

四种视窗分别为:终端富文本、HTML、控件以及文件。

首先介绍一下运行结果的控制。程序重新运行后运行结果将重新产生,或者通过清屏的控制字符也可以清空已有的运行结果,例如下面的bash程序演示了如何控制运行结果。

1
2
3
4
5
6
7
echo -e "first line"
sleep 1
echo -e "second line"
sleep 1
echo -e "\033[2Jthird line"
sleep 1
echo -e "forth line"

终端富文本

终端通过控制字符可以生成富文本,Anchor支持相关的大部分控制字符,但不支持光标移动类的控制字符。

所有正常输出内容将默认作为终端富文本处理。

例如下面的bash程序演示了生成一个字符日历:

1
cal | head -n 1; cal -h | tail -n +2 | grep --before-context 6 --after-context 6 --color=always -e " $(date +%e)" -e "^$(date +%e)"

可以看到当日的日期如终端中一样进行了标红。

HTML

Anchor支持HTML,要求的输出格式为:

1
{ "type": "html", "data": "<div>body</div>" }

由于自动计算高度的问题,HTML要求去除最外层的<html>标签,直接使用<head> / <body>标签。

例如下面的bash程序演示了一个简单的HTML视窗:

1
2
3
4
echo '{
"type": "html",
"data": "<body><script>function s(){document.getElementById(\"p\").innerHTML=\"greeting from anchor\"}</script><p id=\"p\" style=\"background: gray\">aaa</p><button onclick=s()>Click</button></body>"
}'

控件

Anchor提供四种视窗中可以使用的控件:

种类关键词
滑块slider
文本输入textfield
开关toggle
文本显示text

与HTML类似,使用控件的关键词为tools,数据栏需要填入的内容为:

1
2
3
4
[
["id", "type", "hint", "default"],
["id", "type", "hint", "default"]
]

例如下面的bash程序展示了一个简单的控件视窗:

1
2
3
4
5
6
7
8
9
echo '{
"type":"tools",
"data":[
["id1","slider","first control","10"],
["id2","textfield","second control","text"],
["id3","toggle","third control","true"],
["id4","text","fourth control","content"]
]
}'

操作控件后,控件的属性将作为程序的参数传给程序并再次运行。

文件

文件视窗提供一个接收文件拖曳的窗口,Json格式为:

1
{ "type": "file", "data": "" }

收到文件拖曳后,文件名将作为参数传给程序并再次运行。

多个视窗组合

组合视窗的关键词为multi,所以Json格式为:

1
{ "type": "multi", "data": [] }

例如下面的bash程序演示了一个简单的组合视窗:

1
2
3
4
5
6
7
8
echo '{
"type": "multi",
"data": [
{"type": "html", "data": "<div>html</html>"},
{"type": "tools", "data": [["id1","slider","first control","10"]]},
"plain text",
{"type": "file", "data": ""}
]}'

自定义程序

添加文件访问权限

由于Apple的沙盒策略,Anchor所有访问的文件都需要用户明确授权。

你可以在设置页添加可访问的文件夹,这里给出建议的操作:

  1. 创建~/.anchor文件夹,并将需要访问的资源加入该文件夹
  2. 将该文件夹在设置页中添加可访问
  3. 在测试页通过测试代码ls /Users/name/.anchor确认可以访问

更改使用的程序

这里以Python为例演示使用其他程序。

在程序栏,更改为python

在代码栏,输入如下代码:

1
2
import sys
print(sys.version)

将显示Python的系统信息。

这里另外演示通过osascript进行弹窗的操作。

在程序栏,更改为bash

在代码栏,输入如下代码:

1
osascript -e 'display notification "hello world!" with title "Greeting" subtitle "More text" sound name "Submarine"'

调用其他可执行文件

由于沙盒策略,软件即使可以访问也无法运行系统自带以外的可执行文件。

所以需要通过一个代理完成可执行文件的访问,也就是Anchor访问代理软件,代理软件运行可执行文件。

通过代理软件的中转,Anchor就可以完成可执行文件的调用。

按照我的使用习惯(虽然我也可以使用本地的非沙盒版本),我会将代理软件与Anchor设置开机自动运行。

代理软件非常简单,只需要一个本地的HTTP服务器以及一些调用可执行文件的命令,如果你想要使用我已经写好的软件,可以直接到我的Github中进行下载。

FAQ

Q: 后续是否会支持Widget以及桌面小组件。

A: 在Big Sur正式更新后就会更新Widget,后续也将增加桌面小组件的功能。这些功能都将在普通版中提供,专业版与普通版的区别将保持仅有自定义界面与自定义代码。

Q: 通知中心的内容没有更新。

A: 只有在主程序启动的情况下组件的任务才会运行,所以请检查主程序是否已经运行。

Q: 是否有一些常用的功能组件提供?

A: 目前这些内容都放在Github上,后续如果用户量需要一个专门的网站了,届时会更新独立的网站。根据用户反馈非常常用的功能,将会更新到预定义组件里。当然有一些例如“显示隐藏文件”等系统已经有非常便捷的解决方案的功能(快捷键为:Command+Shift+.)将不会更新。

即刻寻找果果活动技术向剧透收集

作者 Roy
2020年6月10日 12:19

即刻在2020年6月10号上午回归了,回归还做了一个特别有意思的活动,寻找果果。活动本身的解谜即友都有做汇总,技术上很多即友也发现了可以剧透的内容,这里做一个收集。本文需要一点点基本的前端知识,算是另一个视角下的解谜。

第零天:预览关卡内容

第零天,官方发布了一张污损的海报预告有一个大型解谜活动,通过补全二维码可以获得活动入口。

1
https://h5.codefuture.top/hybrid-gone-cat

由于活动于第一天开始,所以无法进入活动,甚至因为界面设置问题无法看到规则。

通过浏览页面JS可以发现这样一些代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 代码段一
baseURL: W ? "/proxy" : "https://meow.codefuture.top/api",
withCredentials: !0,
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
// 代码段二
list: function(e, t) {
return regeneratorRuntime.async((function(n) {
while (1)
switch (n.prev = n.next) {
case 0:
return n.abrupt("return", $("/chapters/list", {
skip: e,
limit: t
}));
case 1:
case "end":
return n.stop()
}
}
))
}
// 代码段三
setPollingData: function(e, t) {
e.cats.data = t.data,
e.cats.currentIndex = t.currentIndex
}
// 代码段四
var t = JSON.parse(localStorage.getItem("gone-cat-intro") || "false");
if (t && 0 !== e.currentIndex) {
if (void 0 === e.currentIndex)
return void H.replace("/prev");
if (6 !== e.currentIndex)
return void H.replace("/ch/".concat(e.currentIndex));
H.replace("/final6")
} else
0 !== e.currentIndex && (H.replace("/intro"),
localStorage.setItem("gone-cat-intro", "true"))

通过代码段四可以分析出通过currentIndex判断页面,通过代码段二、三可以发现该值通过/chapters/list获取,通过代码段一可以获得完整的地址https://meow.codefuture.top/api/chapters/list

虽然当时返回的是空值,但通过代码段三可以大致猜测一下返回值的结构,应该类似:

1
{"data":[{"index":6,"feeds":[{"content":"content"}]}],"currentIndex":6}

通过伪造返回值,将这段数据返回给网页,就可以在第零天预览规则和全部关卡的美工。

具体关卡的内容就需要等待上线后后端服务器在content字段中返回。这段数据可以获得这样一个界面:伪造数据预览

第一天:获得两个彩蛋

第一天通过宠语翻译器可以进入第一个彩蛋,网址为:

1
https://h5.codefuture.top/hybrid-gone-cat-eggs/translator-egg.html

在这个网站的头部,可以看到有由于失误加入的其他代码。

1
2
3
4
<link as="style" href="https://static.codefuture.top/hybrid-gone-cat-eggsstatic/css/calendar-egg.132d081f1832e4ca3ac6.css" rel="preload">
<link as="style" href="https://static.codefuture.top/hybrid-gone-cat-eggsstatic/css/translator-egg.b14590d3779db1625490.css" rel="preload">
<link as="script" href="https://static.codefuture.top/hybrid-gone-cat-eggsstatic/js/calendar-egg.8fee2dc09008986e5c9f.js" rel="preload">
<link as="script" href="https://static.codefuture.top/hybrid-gone-cat-eggsstatic/js/translator-egg.5f9b4347afea071ef8c4.js" rel="preload">

所以可以猜到第二个彩蛋和calendar有关,虽然这些文件存在错误,但是通过第一个彩蛋js和css正确的网址可以拼接出第二个彩蛋的js和css文件。

伪造第二个彩蛋

于是可以通过第二个彩蛋的css文件找到第二个彩蛋的猫图:

1
https://static.codefuture.top/hybrid-gone-cat-eggs/static/image/calendar-egg.png

通过将第二个彩蛋的css当成第一个彩蛋的css返回,将页面中的一改成二,就可以伪造出第二个彩蛋的页面。当然,如果第二个彩蛋的提示更改了那也是没办法的事情。

获取彩蛋网址

在第二个彩蛋的js中,可以找到这样的代码:

1
2
3
4
5
6
i.default.updateShareInfo({
title: "GoneCat解谜日历",
desc: "我在GoneCat解谜日历寻找线索,帮帮忙!",
imgUrl: "https://static.codefuture.top/hybrid-decrypt-calendar/img/home-center.c10e5681.png",
link: location.href
})

结合第一天翻译器及彩蛋页面的域名:

1
2
https://h5.codefuture.top/hybrid-cat-translator
https://h5.codefuture.top/hybrid-gone-cat-eggs/translator-egg.html

不难猜出第二天的活动页面及彩蛋域名:

1
2
https://h5.codefuture.top/hybrid-decrypt-calendar
https://h5.codefuture.top/hybrid-gone-cat-eggs/calendar-egg.html

不过第一天的时候这两个页面由于时间没到没有开,会返回403。

但在第二天活动没开始之前页面就可以访问了,并且和伪造的第二个彩蛋没有区别。

第三天:提前进入回归页面

对于重要的内容和文件一般都会有个提醒和监控,具体实现可能不一样,用着习惯就好。

第零天由于没有预告几点开始活动,当时是监控这个网址的返回值变化做的活动开启提醒:

1
https://meow.codefuture.top/api/chapters/list

通过监控主活动页面的JS,可以发现final的JS有更新一开始内容为:

1
var g = ["喵~喵~喵呜呜呜~", "汪~汪~汪汪", "吱~吱吱", "喵~喵呜呜~"],

后来更新为:

1
var g = ["喵~喵~喵呜呜呜~"]

第一天就把这一段内容在翻译器中试过,所以活动的时候也没想到这就是最终线索。

但回头回顾这个活动,翻译器返回值每天都更新,第三天翻译器返回值有更新也在情理之中。

也不知道这个返回值是南北大战活动结束更新还是定时更新。

其他彩蛋

即刻的后端返回HTTP数据包,”x-server”一项返回的是”Potato-Server”。

南北大战活动的解锁需要给babel-t单条点赞600,结果点了一天没点满,估计是故意的。最后第二天十点满了500点赞以后babel-t发了一张歌手伍佰的图,开启了南北大战活动。对,就是那个所以暂时将你眼睛闭了起来。

最后一页还更新了一张图,可惜的是,常规方法大部分人没办法看到,这里一起放出来:

thank-cat

其余彩蛋如果有人分享再继续更新,恭喜即刻回归 🎉!

借助amis通过json构建前端页面

作者 Roy
2020年5月27日 22:19

前端框架越来越多,构建页面需要写的代码一天比一天少。绝大部分人都想过拖控件写前端、只写配置文件写前端、根据内容自动生成前端等。最近因为朋友的关注,无意中发现一个百度开源的通过json生成前端的框架,据说百度内部已经在生产环境中使用,是个功能很健全的框架,这里做个试用和记录。

基本用法

通过json构建前端,不难理解是怎么一回事,以一个简单的提交表单为例。

如果用json描述这个表单,是类似这样的东西:

1
2
3
4
5
6
7
8
9
10
{
"type": "page",
"body": {
"api": "",
"type": "form",
"title": "联系我们",
"controls": [{ "type": "text", "label": "姓名", "name": "name" }],
"actions": [{ "label": "发送", "type": "submit", "primary": true }]
}
}

如果去掉用于布局和美化的各种内容,就应该生成这样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="title>
<span>联系我们</span>
</div>
<div class="body">
<form class="form">
<div>
<span>姓名</span>
<input name="name" placeholder="" type="text">
</div>
</form>
</div>
<div class="footer">
<button type="submit">
<span>发送</span>
</button>
</div>

这样的话,如果有足够的模板,构建前端的内容就会非常的方便,而amis就是这样的一个项目。

实际操作

amis可以实现我可以想象到的大部分页面,复杂的功能可以在有具体的需求的时候借助文档完成。

这里就把搭脚手架和怎么通过json增加一个最简单的页面做一个演示,复杂的页面实际上也就是个复杂的json而已。

我把添加简单页面的操作都放在一次提交中了,完整的内容可以看这里

设置一个页面

这个项目页面都放在routes/admin中,所以我们新建routes/admin/demo/Third.tsx

然后把用来构建页面的json放进这个页面里,所以文件会是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import schema2component from "../../../utils/schema2component";

const schema = {
"type": "page",
"body": {
"api": "",
"type": "form",
"title": "联系我们",
"controls": [{ "type": "text", "label": "姓名", "name": "name" }],
"actions": [{ "label": "发送", "type": "submit", "primary": true }]
}
};

export default schema2component(schema);

给页面添加入口

页面的主页是routes/admin/index.tsx,而侧边栏定义在navigations变量里,我们只需要加一些内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
label: '新增表单',
icon: 'glyphicon glyphicon-ok',
children: [
{
label: '二级表单',
children: [
{
label: '三级表单',
path: 'demo/third',
component: ThirdForm,
},
]
}
]
}

这里用到了ThirdForm,在文件头上加一个引用。

1
import ThirdForm from './demo/Third';

就这么简单就完成了。

成果展示

如果你想自己尝试,可以从我的github下载本项目,然后运行,账号密码都填1即可。

1
2
npm install
npm run start

如果你不愿意本地搭这个环境,在docker里运行或者直接看个图也行,最后的效果是这样的:

效果演示

结语

完成一件事情变得越来越容易,甚至普通人动动手就能完成,就会有担心大量程序员不再被需要。

今天看到阮一峰博客上的一段话。

30年前,开发图形界面 GUI 很困难,Visual Basic 改变了这一点。
20年前,制作一个 Web 应用很困难,PHP 改变了这一点。
10年前,写一个复杂的网页布局很困难,Bootstrap 改变了这一点。
历史上,每当一个领域出现大量需要编程解决的问题,就会诞生一个通用的解决方案,解决掉90%的场景。然后,这个领域对程序员的需求就会快速减少。

我们没有办法阻止分工的逐渐变化,旧的技术越来越快的被替代、淘汰,但熟悉旧技术的人同时也有着在新分工下的优势。

类比而言,我们现在想吃面只需要开门拿个超市快递烧了水泡一下就有热腾腾的面吃。但这不意味着了解种植、制面工艺的人拥有的技能就不再被需要。自动化的种植需要手工种植的经验,种植的品种也可以有更多的精力去研究,了解工艺的人即使是做宣传也会有超出常人的优势。

我们现在熟悉的前端代码可能有一天都会被觉得非常硬核,只有少许专精于框架维护的开发者会去使用。学不动了就转岗去管理了。

import antigravity

另一方面,每一个普通人都实实在在的有了更多的力量,借助飞机,哺乳动物以900公里/小时移动,而哺乳动物需要做的只是走上去坐下。

实现可被链接调用的IOS应用

作者 Roy
2020年4月10日 16:55

我经常通过链接调用本地应用,比如支付宝的扫码支付,向特定用户发送短信。甚至一些隐藏的功能也以链接作为入口,例如Jellow现在的综合搜索。IOS上有两种实现调用的方法,分别是URL Scheme和Universal Links。本文实现了一个简单的Swift项目,实现并介绍这两种功能。

本文需要基本的Swift基础,接触过SwiftUI,大概就是官方例子大致看过的水平即可。

URL Scheme

简介

这种链接是scheme://data的形式,IOS会根据本地安装的应用搜索scheme,找到后询问你是否要通过相关应用打开该链接。很常用的一个链接是微信的扫码weixin://scanqrcode,通过这个链接直接打开微信的扫码。当然,首先需要本地安装了微信。它让类似捷径等一些自动化软件的功能大大增加。

有部分的Scheme是被保留的,可以通过这些保留的Scheme调用一些系统功能,例如短信、FaceTime,这里介绍了有哪些以及具体怎么使用这些内容,有兴趣可以尝试一下。

如果想要了解应用对应的scheme是什么,解压缩ipa,然后在Info.plist文件的CFBundleURLSchemes字段就是设置的scheme。但要了解scanqrcode之类的具体应用,就麻烦的多,网上如果没有现成的内容就需要反编译去找了。

展示收到的内容

我试了试用SwiftUI来写这个小东西,是个很简单的过程。

新建一个项目以后,在ContentView.swift里面加上基本的显示UI即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import SwiftUI

class LinkData: ObservableObject {
@Published var links :[Link]
init() {
self.links = [Link]()
}
func append(link: Link) {
self.links.append(link)
}
}

struct Link {
var time: String
var data: String
var fromApp: String
var linkType: LinkType

enum LinkType: String {
case urlScheme = "URL Scheme"
case universal = "Universal link"
}
}

struct NavigateList: View {
@ObservedObject var linkData: LinkData

var body: some View {
VStack {
NavigationView {
List{
ForEach(linkData.links, id: \.time) { link in
Row(link: link)
}
}
.navigationBarTitle("链接列表")
}
}
}
}

struct Row: View {
var link: Link

var body: some View {
VStack {
HStack {
Text(link.fromApp)
Spacer()
Text(link.time)
}
HStack {
Text(link.linkType.rawValue)
Spacer()
Text(link.data)
}
}
.padding()
}
}

struct ContentView: View {
@ObservedObject var linkData: LinkData

var body: some View {
NavigateList(linkData: linkData)
}
}

这里可以加一个按钮试一试添加是否成功,那么就把NavigateList加一点东西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct NavigateList: View {
@ObservedObject var linkData: LinkData

var body: some View {
VStack {
NavigationView {
List{
ForEach(linkData.links, id: \.time) { link in
Row(link: link)
}
}
.navigationBarTitle("Deep links")
}
Button(action: {
self.linkData.append(
link: Link(
time: "now",data: "data",
fromApp: "Demo App", linkType: .urlScheme
)
)
}) {
Image(systemName: "square.and.arrow.up")
}
}
}
}

按一下按钮,我们就假装收到了一条链接。

响应调用

应用需要决定自己响应什么scheme,这个在XCode > Project > Info > URL Types里面设置。

按照下面的图完成设置就行了。

设置Scheme

这里完成设置以后,通过测试机子的Safari,地址栏填写geoffscheme://就已经能打开应用了。

获取链接内容

这边通过修改SceneDelegate的方式接收数据,这里可能有两种情况。

不过首先,先把URL转换成显示用的Link的方法作为SceneDelegate的类方法写好。

1
2
3
4
5
6
7
8
9
10
11
func parseUrl(_ urlContext: UIOpenURLContext) -> Link {
let fromApp = urlContext.options.sourceApplication ?? "Unknown"
let url = urlContext.url.absoluteString
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyy-MM-dd HH:mm:ss"

return Link(
time: dateFormatter.string(from: Date()), data: url,
fromApp: fromApp, linkType: .urlScheme
)
}

第一种情况,如果应用没有打开,也就是后台也没有该应用,那么链接会调用scene(_:willConnectTo:options)方法。所以就给SceneDelegate添加一个属性和一个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var rootView: ContentView?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
self.rootView = ContentView(linkData: LinkData())

if let urlContext = connectionOptions.urlContexts.first {
self.rootView?.linkData.append(link: parseUrl(urlContext))
}

if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: self.rootView)
self.window = window
window.makeKeyAndVisible()
}
}

另一种情况,如果应用被打开了或者在后台,那么链接会调用scene(_:openURLContexts)方法。所以再添加一个方法。

1
2
3
4
5
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let urlContext = URLContexts.first {
self.rootView?.linkData.append(link: parseUrl(urlContext))
}
}

然后通过在Safari中键入geoffscheme://demo或者一些别的geoffscheme://开头的内容,应用都可以被正确唤起并处理链接的内容。

Universal Links

这种链接是https://yourwebsite的形式,它需要有一个支持HTTPS的域名并进行相关的配置。由于其安全性高,支付宝扫码支付、购买转跳等都是用的这种方式。

需要注意的是,这个方法需要一个加入苹果开发者的账号,所以这个内容还属于年费$99付费解锁。如果手头没有这样的账号的话,其实自己玩的应用URL Scheme也够用了。

设置AASA

以我的网站为例,如果我的应用要通过https://geofftools.cn调用,那么我需要提供一个这样的文件:

1
2
3
4
5
6
7
8
9
10
11
{
"applinks": {
"apps": [],
"details": [
{
"appID": "teamid.geoff.myapp",
"paths": ["*"],
}
]
}
}

这个文件可以放在两个位置,https://geofftools.cn/apple-app-site-association或者 https://geofftools.cn/.well-known/apple-app-site-association

其中Apps按要求留空(官方要求),appID是TeamId和BundleId合起来,path的路径里可以用* ? NOT

响应调用

应用需要设置响应的网站,这个在XCode > Project > Capability > Associated Domains里面设置。没有加入付费的苹果开发者的话,这就没办法设置了。

这边填<service>:<fully qualified domain>,例如applinks:geofftools.cn

获取链接内容

和上面的操作类似,这里添加这样一个类方法。

1
2
3
4
5
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if let urlContext = userActivity.webpageURL.first {
self.rootView?.linkData.append(link: parseUrl(urlContext))
}
}

对比

Universal Link更复杂也有更多的应用,URL Scheme则很容易配置也能满足基本需求,区别做成表格的话大概是这样一些不同。

Safari二次确认需要本地安装调用争抢需要后端配合需要加入开发者
Universal Link××
URL Scheme××

如果考虑安全性和易用性,肯定是Universal Link更合适,他调用应用不必须经过Safari,调用的名称也不会被其他应用占用。但如果只是做个小彩蛋,或者自己实现一些简单的功能,URL Scheme也是完全可以使用的选择。

简化scp命令文件上传下载

作者 Roy
2019年8月10日 22:31

我难免会用别人的电脑连接远程服务器,每到这时传文件都显得非常麻烦。一般也不去另外装东西,就用scp命令传文件,每次都要把服务器、用户名、配置的内容打进去。传一次,打一次,手残打错或者上翻命令的时候,真是格外崩溃。最理想的状态,传文件也可以有一个简单的方法,放在点文件里面。这篇文章就是这个配置,通过点文件让scp文件上传下载简化成 scp pull local_file remote_file。

本文主要记录了放进点文件的代码的内容,附带的把设置ssh密钥对也放在文章后面。如果熟悉这些内容,可供复制的代码直接在效果展示一节。

效果展示

文章开始,照例展示一下成品,将本文的代码放到点文件(.bashrc)里之后,就可以通过自定义的scpp命令完成文件的上传下载,看上去会是这样的:

1
2
scpp push -r local_folder ./
scpp pull remote_file local_file

为了方便直接取用,如果不想看具体的文章,将下面这行代码添加到点文件当中,更改用户名、地址、配置即可:

1
scpp(){(read U S P<<<'user ip ~/tmp/';O='-P 22';H='usage:   scpp [push|pull] [scp options] source target';if [ $1 != 'push' ]&&[ $1 != 'pull' ]||[ $# -lt 3 ];then echo $H;return 2;fi;[ $1 = 'push' ]&&scp ${@:2:$#-3} $O ${@:(-2):1} $U@$S:$P${!#};[ $1 = 'pull' ]&&scp ${@:2:$#-3} $O $U@$S:$D${@:(-2):1} ${!#})}

简化scp命令

scp命令和ssh一起都会装好,但是使用的时候看上去都是这样的:

1
2
scp -P 22 package.tar.gz username@server_ip:~/tmp/package
scp -P 22 -r username@server_ip:~/tmp/logfile logfile

每次上传下载都会需要把配置和用户名、地址完整打一遍,即使上翻命令然后改也非常不方便。

其实重复的内容有三个,用户名、地址、配置,有时候我会把他们添加到环境变量里,于是就变成:

1
2
scp $OPTIONS package.tar.gz $USERNAME@$SERVER:$DEFAULT_PATH/package
scp $OPTIONS -r $USERNAME@$SERVER:$DEFAULT_PATH/logfile logfile

虽然不会省很多事儿,但起码不会输错了,如果变量名无所谓阅读的话还是能省不少事儿的。

为了进一步简化,就干脆写了个函数:

1
2
3
4
5
6
7
8
9
10
11
function scpp () {
local USERNAME='username'
local SERVER='server_ip'
local OPTIONS='-P 22'
local DEFAULT_PATH='~/tmp/'
local USAGE="usage: scpp [push|pull] [scp options] source target"

if [ $1 != 'push' ] && [ $1 != 'pull' ] || [ $# -lt 3 ]; then echo $USAGE; return 2; fi
[ $1 = 'push' ] && scp ${@:2:$#-3} $OPTIONS ${@:(-2):1} $USERNAME@$SERVER:$DEFAULT_PATH${!#}
[ $1 = 'pull' ] && scp ${@:2:$#-3} $OPTIONS $USERNAME@$SERVER:$DEFAULT_PATH${@:(-2):1} ${!#}
}

将这个函数放进点文件(.bashrc),重新加载即可。

当然,需要把用户名、地址、常规配置、默认远端文件夹改成你需要的内容。

1
source .bashrc

加载了这个函数后,上传下载就简化成这样了:

1
2
scpp push package.tar.gz ./
scpp pull -r logfile logfile

当然根据需要,完全可以再更改完配置后把代码简化成一行,点文件加一行代码就可以达到这个效果。

设置密钥对

经常忘记免密码登录需要设置的那个密钥对,这里顺带也做一个记录。

1
2
3
cd ~/.ssh
ssh-keygen -t rsa -C "geoff@home" -N "" -f id_rsa
cat ~/.ssh/id_rsa.pub | ssh username@server 'cat >> ~/.ssh/authorized_keys'

简单的来说就是创建密钥对然后放进去,如果还是不行,检查一下文件权限和ssh设置。

1
2
cat /etc/ssh/sshd_config | grep RSAAuthentication
cat /etc/ssh/sshd_config | grep PubkeyAuthentication

scp和ssh就是类似的东西,免密码登录不需要额外的设置。

为scpp设置自动补全

最简单的方式,可以使用complete命令添加自动补全。
先提供完整的补全命令,放在.bashrc中即可:

1
_scpp(){ [[ ${#COMP_WORDS[*]} -le 2 ]]&&COMPREPLY=($(compgen -W 'pull push' ${COMP_WORDS[1]}))||COMPREPLY=($(compgen -A file)); };complete -F _scpp scpp

下面分解来解释一下命令里有些什么。
通过如下命令让_scpp函数处理scpp的自动补全:

1
complete -F _scpp scpp

处理函数中常用的变量有这些:

variabledescription
COMP_WORDS类型为数组,存放当前命令行中输入的所有单词
COMP_CWORD类型为整数,当前输入的单词在COMP_WORDS中的索引
COMPREPLY类型为数组,候选的补全结果
COMP_WORDBREAKS类型为字符串,表示单词之间的分隔符
COMP_LINE类型为字符串,表示当前的命令行输入字符
COMP_POINT类型为整数,表示光标在当前命令行的哪个位置

函数最后设置的COMPREPLY数组会通过<TAB>完成补全。
更具体的补全就是简单的文档内容,可以在这里扩展阅读。

PPT中使用ECharts实现可拖拽关系图

作者 Roy
2019年7月22日 22:31

Office中有简单的自定义图表,但很多时候无法满足使用的要求。地图、关系图、坐标图等复杂的表无法绘制,图表中数据点拖拽、动态数据、数据在展示中的自由筛选等交互无法完成。本文通过ECharts在PPT中引入更现代的图表功能,让图标更加丰富、美观、可交互。关于图表的基本使用,文中提供了关系图可拖拽版本的实现,方便了复杂关系的显示。

基础思路

这篇文章的基本问题是这个:

我需要在PPT中展示一个非常复杂的关系,图包含大量节点和连线。关系图可以根据演示的情况实时变化,方便讨论。

所以大致思路是这样的,在PPT中添加网页控件,在网页控件中通过ECharts绘制需要的图形。过程中碰到的主要问题有:

  • PPT在插入控件、运行控件时产生不必要的提示
  • ECharts图表未提供符合要求的拖拽,拖拽后产生回弹或整体重排

完成后的效果可以见这里

为了方便测试,这里提供了完整项目的下载,需注意项目中未包括基础HTML一节中应下载的echarts-en.js,额外下载即可。

PPT设置

PPT的设置为,添加控件、控件自动运行、消除不必要提示,最终达到与原生表格没有差别的体验。

为了方便下面的叙述,以下内容均运行在Windows 10,Office 2016,文件列表如下:

1
2
3
4
5
drag-demo
|- main.pptm
|- main.html
|- main.js
`- echarts-en

添加控件

在开发工具一栏中,选择其他控件,其中可以找到Microsoft Web Browser,点击确定。

但因为ActiveX的关系,这边一般都会提示,“无法插入此ActiveX控件”。

为了解决问题,需要更改如下注册表(Win+R regedit),允许插入控件。如下键值若没有,新建即可,全部改为0。Office如果不是16的话根据Office版本更改最后一条。

1
2
3
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\ActiveX Compatibility\{8856F961-340A-11D0-A96B-00C04FD705A2}\Compatibility Flags
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Internet Explorer\ActiveX Compatibility\{8856F961-340A-11D0-A96B-00C04FD705A2}\Compatibility Flags
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\REGISTRY\MACHINE\Software\Microsoft\Office\16.0\Common\COM Compatibility\{8856F961-340A-11D0-A96B-00C04FD705A2}\Compatibility Flags

由于IE版本可能不同,这里还需要限制Web Browser使用特定版本的IE,将下面的键值改为十进制的10001。

1
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION\PowerPnt.exe

以上内容参考该指南,如果设置了还是不能成功插入可以进一步阅读指南。

控件自动运行

双击该控件可以打开VBA的编辑窗口,将如下代码添加到最开头。

1
2
3
4
5
6
Sub OnSlideShowPageChange()
Select Case ActivePresentation.SlideShowWindow.View.CurrentShowPosition
Case 1
WebBrowser1.Navigate (ActivePresentation.Path + "\main.html")
End Select
End Sub

这就是一个简单的VBA,每次切换页面都会调用OnSlideShowPageChange函数,在该函数中进行判断,若页码为Web Browser所在的页码则将页面转到本地的网页主页。

消除不必要提醒

即使这样操作后,网页主页加载本地的JS还是会提示,“为帮助保护你的安全,你的Web浏览器已经限制此文件现实可能访问你的计算机的活动内容。单击此处查看选项”。虽然所有JS写进网页里也是个办法,但终究不方便。这里可以通过在HTML中DOCTYPE后加入如下的一行,规避这一提醒。

1
<!-- saved from url=(0013)about:internet -->

全部完成之后由于使用的VBA,需要将PPT保存为“启用宏的PowerPoint演示文稿”(main.pptm),另存即可。

ECharts设置

ECharts设置为基础HTML,生成关系图,添加拖拽。

由于IE的兼容性问题,以下的代码都经过了一些例如用var替换const的操作,这也没办法。如果你知道如何将Chrome作为控件加入PPT,请一定留言告诉我。

基础HTML

基础的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<!-- saved from url=(0013)about:internet -->
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="echarts" style="width:100%;height:800px;"></div>
<script src="echarts-en.js"></script>
<script src="main.js"></script>
</body>
</html>

body中添加一个div用于显示图表,之后引入ECharts和配置文件。

echarts-en.js是ECharts包,为了方便离线使用,这里下载到了本地(下载地址见这里)。

生成关系图

ECharts的图形生成是通过设置Option字典完成的,也就是根据文档创建一个Option字典,通过chart.setOption(option)完成设置。

关系图对应的是series项下类型为graph的内容,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
var BASE_GRAPH_ID = 'relationship'
var chart = echarts.init(document.getElementById('echarts'));

var nodes = [
{ name: '节点1', value: [100, 200] },
{ name: '节点2', value: [300, 200] },
{ name: '节点3', value: [200, 100] },
{ name: '节点4', value: [200, 300] },
]

var links = [
[ [0, 1], '关系1', 0.2 ],
[ [1, 0], '关系2', 0.2 ],
[ [0, 2] ], [ [1, 2] ], [ [1, 3] ], [ [0, 3] ],
];

links = echarts.util.map(links, function(l) {
return {
source: l[0][0],
target: l[0][1],
label: l[1]?{
show: true,
formatter: l[1],
}:{},
lineStyle: l[2]?{ curveness: l[2] }:{}
}
})

var dataOption = {
title: { text: '可拖拽关系图' },
animation: false,
series : [{
id: BASE_GRAPH_ID,
type: 'graph',
coordinateSystem: 'cartesian2d',
symbolSize: 50,
label: { show: true },
edgeSymbol: ['none', 'arrow'],
edgeSymbolSize: [0, 10],
data: nodes,
links: links,
lineStyle: { width: 2 }
}],
xAxis: {
min: 0,
max: 400,
type: 'value',
show: false,
},
yAxis: {
min: 0,
max: 400,
type: 'value',
show: false,
},
};

chart.setOption(dataOption);

添加拖拽

由于其他布局都存在回弹或者自动重新排列的问题,这里没办法使用force布局提供的拖拽。也无法使用默认布局,默认布局在拖拽一个节点后其他节点会重新排布。所以最后我们选择坐标系布局,每一个节点预先设置位置,在坐标数量多的情况下也可以随机数或者借助另一个默认布局,确定节点的位置。

二维坐标的关系图节点没有默认的拖拽,而一般的图形均有拖拽的默认选项。所以实现的思路为,在关系图每一个节点上覆盖一个透明的可拖拽图形,拖拽透明图形的时候同步更改节点的位置。

实现代码如下,需要复制在上述代码后面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var dragOption = {
graphic: echarts.util.map(nodes, function (item, i) {
return {
type: 'circle',
shape: { r: 25 },
position: chart.convertToPixel({seriesId: BASE_GRAPH_ID}, item.value),
invisible: true,
draggable: true,
z: 100,
ondrag: echarts.util.curry(onPointDragging, i),
}
}),
};

function onPointDragging(index) {
newPosition = chart.convertFromPixel({seriesId: BASE_GRAPH_ID}, this.position);
nodes[index].value = newPosition;
chart.setOption({
series: [{
id: BASE_GRAPH_ID,
data: nodes
}]
});
}

chart.setOption(dragOption);

其中涉及的一个问题是关系图的布局和单独图形的坐标系统不同,同样的[100, 100]在两个坐标系统中位置是不一样的。单独图形是直接根据像素确定的,关系图则有图形专门的坐标系统。通过convertToPixelconvertFromPixel可以完成转换。

我这里是使用Chrome完成的Debug,全部结束之后运行main.pptm,再放映幻灯片。

畅言PC版emoji显示

作者 Roy
2019年5月23日 08:24

畅言目前PC版的评论中无法正常显示emoji,大致搜了搜网上也没有相关的插件或者官方文档。大致的规划一下也就是写一段替换的代码,找到回调函数就完成了。本文在我也不知道愿不愿意透露姓名的陈大佬的指导下完成,我主要就是写了个测试环境,可以说非常愉快了。

本文主要列出替换的JS,简述了一下找回调函数的思路。

替换代码

畅言的emoji传到前台的数据是[emoji:d83cdfc6]格式。
所以我们需要做的就是把这一段文字转化成Unicode字符,这是个很简单的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 限定只更新畅言评论栏中的内容
divList = document.getElementById("SOHUCS").getElementsByClassName("wrap-word-gw");
for (var divIndex = 0; divIndex < divList.length; divIndex++) {
div = divList[divIndex]
// 畅言emoji后台存储为: [emoji:d83cdfc6]
for (var emoji, regex = /\[emoji:(.*?)\]/g, l = []; null != (n = regex.exec(div.innerHTML)); ) {
f = "\\u" + n[1].substring(0, n[1].length / 2),
s = "\\u" + n[1].substring(n[1].length / 2, n[1].length),
emoji = f + s,
emoji = unescape(emoji.replace(/\\u/g, "%u"));
// 存储检测到的emoji至暂存列表
l.push(emoji)
}
// 将检测到的emoji完成替换
for (var i = 0; i < l.length; i++) {
div.innerHTML = div.innerHTML.replace(/\[emoji:(.*?)\]/, l[i])
}
}

替换思路

下面问题就在于能不能找到回调函数,在评论载入完成后调用替换。
目前暂时没有找出可用的回调,现在最简单的使用window.onload = function(){}完成了替换。
所以目前的结果就是页面载入完成后替换,->这里<-是代码。

我发现upload/changyan.js中有changyan.api.ready的函数。

1
2
3
4
window.changyan.api.ready = function(fn) {
window.changyan.api.tmpHandles = window.changyan.api.tmpHandles || [];
window.changyan.api.tmpHandles.push(fn);
};

之后src/adapter.min.js中通过runReadyFn完成回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var runReadyFn = function() {
if (window.changyan.tmpHandles && window.changyan.tmpHandles.length) {
for (var i = 0; i < window.changyan.tmpHandles.length; i++) {
var _fn = window.changyan.tmpHandles[i];
_fn && _fn();
}
window.changyan.tmpHandles = [];
}
};
window.changyan.rendered = false;
$$event.listen("changyan:cmt-header:header-rendered", function() {
if (!window.changyan.rendered) {
runReadyFn();
window.changyan.rendered = true;
}
});
$$event.listen("changyan:mobile-cmt-list:list-render", function() {
if (!window.changyan.rendered) {
runReadyFn();
window.changyan.rendered = true;
}
});
$$event.listen("changyan:cmt-box:box-render", function() {
if (!window.changyan.rendered) {
runReadyFn();
window.changyan.rendered = true;
}
});

一点吐槽

在研究代码的过程当中,发现代码中还有残留下来的内容。

比如extensions/longloop.js中的:

1
e("C:/Users/Yaodoggy/Documents/Program Files/Wamp/wamp/www-mdevp/mdevp/cache/www/longloop/longloop.js")

畅言评论插件优化

作者 Roy
2019年5月13日 22:24

搜狐旗下有一款评论插件,畅言,PC端与WAP端均适配,目前提供微信、微博、QQ、手机号四种登录方式。为了给博客添加评论功能,趁晚上的空研究了代码插入方式以及CSS美化。本文没什么技术含量,主要提供可以抄的代码,WTFPL。

本来这事儿应该是这周末的安排,不过也是赶巧,下午闲着的时候畅言客服给我来了个电话,闲着也是闲着就写了一半。也是我的风格了,服务器是云服务器客服一个电话我顺带配好的,备案是备案机构一个电话我顺带备好的。随意是一方面吧,这类事儿总体完成的效率还真不低。于是晚上回家就把剩下的一半写掉了,这篇文章没什么理论,主要是简单的配置代码,复制过去有空的时候慢慢看好了。

代码插入

官网有自适应的代码安装方式:

1
2
3
4
5
6
7
8
<div id="SOHUCS" ></div> 
<script type="text/javascript">
(function(){
var appid = '这里填写自己的appid';
var conf = '这里填写自己的conf';
var width = window.innerWidth || document.documentElement.clientWidth;
if (width < 960) {
window.document.write('<script id="changyan_mobile_js" charset="utf-8" type="text/javascript" src="https://changyan.sohu.com/upload/mobile/wap-js/changyan_mobile.js?client_id=' + appid + '&conf=' + conf + '"><\/script>'); } else { var loadJs=function(d,a){var c=document.getElementsByTagName("head")[0]||document.head||document.documentElement;var b=document.createElement("script");b.setAttribute("type","text/javascript");b.setAttribute("charset","UTF-8");b.setAttribute("src",d);if(typeof a==="function"){if(window.attachEvent){b.onreadystatechange=function(){var e=b.readyState;if(e==="loaded"||e==="complete"){b.onreadystatechange=null;a()}}}else{b.onload=a}}c.appendChild(b)};loadJs("https://changyan.sohu.com/upload/changyan.js",function(){window.changyan.api.config({appid:appid,conf:conf})}); } })(); </script>

把提供的appid和conf填好,尝试访问就可以用了。

使用的时候发现畅言是可以http访问的,如果你对于全站HTTPS有特别的要求,可以通过返回HTTP请求的时候添加如下内容:

1
Content-Security-Policy: default-src https:

不过因为牵扯到Web版本和PC版本的CSS存在区别,这里的JS代码还需要做一些小的改动。
这类就简单的添加了两个不同的CSS。

1
2
3
4
5
6
7
8
<div id="SOHUCS" ></div> 
<script type="text/javascript">
(function(){
var appid = '这里填写自己的appid';
var conf = '这里填写自己的conf';
var width = window.innerWidth || document.documentElement.clientWidth;
if (width < 960) {
window.document.write('<link rel="stylesheet" type="text/css" href="/changyan-mobile.css">');window.document.write('<script id="changyan_mobile_js" charset="utf-8" type="text/javascript" src="https://changyan.sohu.com/upload/mobile/wap-js/changyan_mobile.js?client_id='+appid+'&conf='+conf+'"><\/script>')}else{window.document.write('<link rel="stylesheet" type="text/css" href="/changyan.css">');var loadJs=function(d,a){var c=document.getElementsByTagName("head")[0]||document.head||document.documentElement;var b=document.createElement("script");b.setAttribute("type","text/javascript");b.setAttribute("charset","UTF-8");b.setAttribute("src",d);if(typeof a==="function"){if(window.attachEvent){b.onreadystatechange=function(){var e=b.readyState;if(e==="loaded"||e==="complete"){b.onreadystatechange=null;a()}}}else{b.onload=a}}c.appendChild(b)};loadJs("https://changyan.sohu.com/upload/changyan.js",function(){window.changyan.api.config({appid:appid,conf:conf})})}}

这段代码使用的两个CSS分别是changyan-mobile.csschangyan.css

CSS美化

畅言默认的样式偏圆,也不是扁平的风格,我大致网上找了找现有的样式做了个修改。

这段代码是changyan.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
.module-cmt-box {
padding: 10px 0!important;
margin-top: -10px!important;
overflow: visible!important;
}

.header-login {
left: 90px!important;
border: 0!important;
border-radius: 0!important;
margin-top: 88px!important;
border-radius: 21px!important;
}

.post-wrap-border-l,.post-wrap-border-r,.post-wrap-border-t-l,.post-wrap-border-t-r {
display: none;
}

.post-wrap-main {
border: 0!important;
}

.post-wrap-w {
background: #f0f0f0;
}

.btn-fw {
background: #5fb878 url(data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTgxNy42NTcgNzc1Ljc1OEw0NTQuNzEgNjY0LjA4bDM2Mi45NS00MTguNzg4TDM0My4wMzIgNjY0LjA4IDYzLjgzNyA1NTIuNDAzIDk1Ny4yNTYgNzcuNzc1IDgxNy42NTcgNzc1Ljc1OHpNNDU0LjcxIDk0My4yNzVWNzQ3Ljg0bDExMS42NzQgNTUuODRMNDU0LjcxIDk0My4yNzR6IiBmaWxsPSIjZmZmIi8+PC9zdmc+) center no-repeat!important;
width: 60px!important;
height: 60px!important;
border-radius: 30px;
margin-top: -5px!important;
margin-right: 40px!important;
background-size: 30px!important;
box-shadow: 5px 5px 10px rgba(0,0,0,.2);
-webkit-transition: .3s;
transition: .3s;
outline: none;
}

.btn-fw:hover {
box-shadow: 4px 4px 10px rgba(0,0,0,.2);
}

.block-head-w,.cmt-list-type {
height: 0px!important;
}

.section-service-w {
height: 0;
opacity: 0;
}

.head-img-w {
margin: 0!important;
}

.head-img-wimg {
width: 25px!important;
height: 25px!important;
}

.head-img-w {
top: 118px!important;
left: 95px!important;
}

.wrap-action-gw {
border-bottom: 1px solid #dee4e9!important;
padding-top: 30px!important;
padding-bottom: 1pc!important;
}

.cmt-list-number,.title-name-gw-tag,.type-lists,.wrap-name-w {
display: none!important;
}

.cmt-list-type {
margin: 0!important;
}

.build-floor-gw {
background: #f0f0f0!important;
}

.block-cont-gw {
padding: 20px 0!important;
border: 0!important;
}

.section-list-w {
width: 95%!important;
margin-left: 2%!important;
}

.build-floor-gw {
background: #f9f9f9!important;
}

.user-name-gw>a {
text-decoration: none!important;
}

这段代码是changyan-mobile.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
.module-mobile-cmt-header {
height: 150px!important
}

.comment-number-tri {
border-left: 0!important
}

.comment-number {
margin-top: 1px!important
}

.comment-number, .comment-text {
background-color: transparent!important;
font-size: 16px!important;
color: black!important;
font-weight: 500!important;
padding: 0!important
}

.header-right {
float: left!important
}

.header-login {
display: inline-block!important;
margin-top: -12px!important;
font-family: sans-serif!important;
font-size: 14px!important
}

.header-pho {
width: 2em!important;
height: 2em!important;
background-size: 2em!important
}

.mobile-header-head {
margin: 85px 0 0 0!important;
height: 0!important
}

.comment-textarea {
height: 0!important;
float: left!important;
margin-top: -150px!important
}

.header-comment-number {
margin: -6px 0 0 10px!important
}

.comment-input {
border: 0!important;
background-color: transparent!important;
font-family: sans-serif!important;
font-size: 14px!important;
border-radius: 0!important;
height: 90px!important;
margin-top: 15px!important
}

.box-footer-button {
background: #b1b1b1 url(data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTgxNy42NTcgNzc1Ljc1OEw0NTQuNzEgNjY0LjA4bDM2Mi45NS00MTguNzg4TDM0My4wMzIgNjY0LjA4IDYzLjgzNyA1NTIuNDAzIDk1Ny4yNTYgNzcuNzc1IDgxNy42NTcgNzc1Ljc1OHpNNDU0LjcxIDk0My4yNzVWNzQ3Ljg0bDExMS42NzQgNTUuODRMNDU0LjcxIDk0My4yNzR6IiBmaWxsPSIjZmZmIi8+PC9zdmc+) center no-repeat!important;
background-size: 30px 30px!important;
width: 100px!important;
height: 50px!important;
border-radius: 10px!important;
margin-right: 10px!important
}

.list-footer-wrapper-wap {
display: none
}

下面那个评论就是已经设置好了的畅言插件了。

之后根据博客的类型将这个CSS文件压缩好每次载入即可。

❌
❌