阅读视图

发现新文章,点击刷新页面。
☑️ ☆

只是组件 使用手册

本文为IOS软件“只是组件”的使用手册。从简单的如何添加组件到像PS一样编辑你自定义的组件,都可以在这里找到帮助。

本文由于内容较多,建议在导航中找到你需要的章节转跳,除了文章开头的图片,其余图片都做了隐藏。

软件介绍

只是组件是一款好看又实用的桌面组件工具,自从2020年10月上架以来,完全的自定义、便捷的设置和方便的转跳受到用户的广泛好评。使用只是组件,你可以拥有10+预设组件,包含相框照片、待办、日期记录等;你可以可视化自定义一个全新的完全不同的组件或者导入他人的组件;你可以将网络数据解析后显示在组件内,点击组件你设定的地方转跳执行特定操作。不仅如此,只是组件所有功能均可以免费使用,仅限制最多已添加组件数量。

image-demo

目前文档在更新中,预计十二月三十一号会完成更新。

希望你喜欢 只是组件!

基础内容

将组件添加到屏幕上

苹果官方给出了一个如何添加小组件的说明:见这里。

这里只需要选择“只是组件”,添加本软件的Widget即可。

如果以添加一个文字Widget为例,分为这几部:

  1. 进入软件主界面,点击“组件列表”,选择文字组件,点击右上角保存
  2. 按照官方添加小组件的方法,添加一个“只是组件”的小组件
  3. 长按“只是组件”的小组件,在弹出的菜单中选择“编辑小组件”
  4. 选择刚添加的文字组件

通过二维码分享组件

软件的所有组件都可以进行导入和导出,包括预设组件和自定义组件。

导入组件有两个入口,一个是桌面软件图标的Force touch,一个是主界面右上角的二维码图标。

导出组件在组件编辑界面右上角,图标为分享按钮。

导入导出都可以选择通过二维码或者通过字符串,如果组件包含的信息量过大,建议通过字符串导入导出。

这里提供一个导入导出简单演示的动图:点这里

使用激活码激活高级版

目前软件赠送的激活码均为单设备永久激活,激活码区分大小写。

激活方式为软件内激活。点击下方关于转到关于页,点击“分享应用”,在“激活码”框中输入。

如果激活码通过验证,稍等片刻软件就将解锁高级版内容。

如果激活失败,请确认软件是否有联网权限以及激活码是否准确没有缺失。

高级内容

创建自定义组件

软件主程在安卓时代曾经花了很多的时间在安卓自定义组件上,可惜苹果上没有那么便捷的自定义,十分遗憾。

所以有了这个类PS的完全自定义组件,可以从头做一个专属组件,也可以导入别人做好的组件。

不过,最首先的一点,在做自定义组件的时候记得随时保存,即点击右上角的对勾。

这里提供一个动图,简单的可以感受一下这个自定义组件的大致内容:这里

如果你愿意花几分钟看一下视频,可以点这里查看一个详细的操作演示:这里

当然,百闻不如一试,充分的自定义只等你来实现想法。

我们也会定期收集优秀的自定义组件,做一个展示和分享,在这里你可以找到许多好看的组件。

欢迎你分享你的优秀作品,可以通过二维码添加交流群或者微博@我

快捷转跳

Widget的一大实用功能就是快捷转跳,通过点击Widget的特定位置,可以打开微信扫一扫、微信支付、健康码、乘车码等等,甚至可以通过捷径实现一系列的自动化操作。

在软件中可以通过预设组件的快捷启动组件和自定义组件的转跳特效完成这一效果。

预设组件的快捷启动较为简单,这里就不多加赘述。自定义组件的添加转跳可以查看这个动图

关于快捷启动应该填写的内容,详细的可以查看这篇文章,软件未来可能也会增加一键添加捷径的功能。

获取网络数据

Widget很大的一个实用性限制就是只能显示一些本地的内容,如果可以便捷的显示任意网络内容,那就实用的多。

所以软件提供了任意网络数据的格式化与显示,你可以通过以下三种格式解析网络数据:

  1. Json
  2. 正则表达式
  3. 各版本RSS

网络数据会根据设定的更新间隔自动后台更新,但后台更新需要注意:

  1. 如果开启了省电模式将停用所有后台更新
  2. 苹果对于软件后台更新的策略是:系统统一安排空闲时间更新,不保证特定时间一定唤起更新
  3. 使用软件次数越多,系统提供后台唤醒的可能越多
  4. 使用快捷转跳后软件会进行必要的数据更新

所以为了保证软件表现正常,请尽量在Widget中使用快捷转跳,否则Widget将被认为是不常用软件,无法后台唤起。

具体的操作我们提供了一个使用演示:这里

其中文本解析中的规则,以数据源名字weather为例,如果数据内容是这样的:

1
2
3
4
5
6
{
"code": 0,
"data": {
"temp": 20,
}
}

那么获取数据中温度的规则就是:

1
2
$(fetch.weather.data.temp)
更新时间:$(fetch.weather)

其余的也是类似的规则,类推可知。

文本解析规则

软件的另一大特色功能(另一大文档大头)是一个文本解析系统,通过类似Excel的简单规则,拼接各种数据,数据我们需要的内容。

我们提供了一个操作的展示:这里

例如一个最简单的时间,规则就是:

1
$(date.HH:mm)

在网络数据一节还看到了关于网络数据的解析规则,除此以外,还有一些系统数据的获取规则。

类型关键词内容示例
电池batterylevel, state, charging, lowpower$(battery.level)
蓝牙bluetooth$(bluetooth)
系统recordcountry, language, reboot, brightness, volume, timezone, name, systemName, systemVersion, model$(record.brightness)
内存memoryall, active, activepercentage, inactive, inactivepercentage$(record.active)
硬盘diskall, active, activepercentage, inactive, inactivepercentage$(disk.active)
Wifiwifiip, ssid$(wifi.ssid)
健康healthstepCount$(health.stepCount)

之后将更新更多数据,文档也将同步更新。

FAQ

Q: 邀请好友后没有马上更新数据。

A: 邀请数据的统计会在每天十二点更新,所以第二天再看就有新的数据了。

Q: 组件有时候会卡住不更新数据。

A: 组件本身刷新有非常小的概率出现这个问题,目前已经尽量降低刷新频率避免发生这个问题。如果的确卡住了,点进去重新保存一下即可。

Q: 后台数据没有按照更新时间准确更新?

A: 首先,请查看是否打开了软件的背景数据刷新,在设置中搜索”只是组件“。背景数据刷新本身不支持准确的时间间隔,系统会根据软件的要求协调各个软件在必要的时候刷新,但总体而言软件使用频率越高,系统会越多分配背景数据的刷新。只是组件支持了各种唤醒方式,尽量保证了刷新。建议配合快捷启动使用软件,使用快捷启动可以保证数据的按时刷新。

☑️ ☆

IOS常用URL Scheme整理

Anchor在Mac版本之后,终于IOS版终于在十一也过审了。IOS版的主要功能就是快捷启动和好看的各种显示,其中快捷启动就是URL Scheme的功能,所以就有了这篇文章,能给你们的使用带来一些便利。

写在前面

关于软件

如果你还没有用过Anchor,我强烈建议你下载,App Store即可免费获取。快捷启动和高度自定义组件合集,这里是软件链接

关于URL Scheme

URL Scheme是每个程序内置的快捷启动,并不一定有特定的格式,是存在一定可能随着某次更新产生更改的。

通过访问该链接,可以转跳相应的软件的相关功能。

部分软件,例如捷径,本身保证URL Scheme有特定的格式。

部分软件,例如微信,不保证URL Scheme内容和格式的稳定,在某次更新后目前只有扫码链接可用,非常糟糕,是常用软件里支持最少的。

这种快捷启动会给日常使用带来大量便利,例如支付宝的扫码、付款码、收款码,各个设置页面等等。

而在Anchor的设置中,每个组件都可以设置URL Scheme产生点击后的后续操作。只需要将下述的字符串输入即可。

系统

用途URL Scheme
钱包shoebox://
邮件mailto://anchorapp@126.com
电话tel://12345678
短信sms://12345678
显示日历calshow://
照片photos-redirect://
设置prefs://

支付宝

用途URL Scheme
扫一扫alipayqr://platformapi/startapp?saId=10000007
打开收款alipays://platformapi/startapp?appId=20000123
乘车码alipayqr://platformapi/startapp?saId=200011235
付款码alipay://platformapi/startapp?appId=20000056
快递查询alipays://platformapi/startapp?appId=20000754
健康码alipays://platformapi/startapp?appId=20000067&url=https%3A%2F%2F68687564.h5app.alipay.com%2Fwww%2Findex.html

微信

用途URL Scheme
扫一扫weixin://scanqrcode

即刻

用途URL Scheme
搜索jike://page.jk/search
用户jike://page.jk/user/6b8159a9-bd2e-435d-ab9d-a8acba6778b0
主题jike://page.jk/topic/[topic-id]
消息jike://page.jk/message/[message-id]

其他软件

用途URL Scheme
微博sinaweibo://
淘宝taobao://
QQ音乐qqmusic://
网易音乐听歌识曲orpheuswidget://recognize
Bilibilibilibili://

捷径

用途URL Scheme
调用捷径shortcuts://run-shortcut?name=[name]&input=[input]
创建捷径shortcuts://create-shortcut
打开特定捷径shortcuts://open-shortcut?name=[name]

调用捷径后,在捷径中可以完成的内容就非常多了。

🔲 ☆

Anchor 专业版手册

本文为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+.)将不会更新。

☑️ ☆

MacOS在NSImage里提供了哪些系统图标

近两周闲下来的时间都在写一个新的App,都没时间写字和写文章。昨天美工大哥问了我一个问题,MacOS到底有哪些系统图标可用?图标文件夹的确是一个解决方案,但保证可用还是得额外加进项目的素材库,和外加的图标没有区别。真的内置的图标的话,这个问题换句话说是NSImage.Name里面到底有哪些图标。本文用看着也越来越重要的SwiftUI,抓取开发者文档的数据展示可用的图标和尺寸。

已有资料

开发者文档里有专门的一页,列出了目前可用的系统图标。但只有名字,没有相应的图片放在边上,也看不到尺寸。

github上发现了一个项目,可惜几年不更新了,图标数据也是写死在代码里的。项目名字叫fucking_nsimage_syntax,不知道是不是也在吐槽开发者文档里不放图和尺寸。

如果自己写一个的话,基本是,网页抓取数据->数据对应图标和尺寸->SwiftUI展示,也很简单。

数据抓取

开发者文档载入是先框架后数据,简单看一下包,可以找到图标页的数据通过获取这一个网址取得:

1
https://developer.apple.com/tutorials/data/documentation/appkit/nsimage/name.json

既然已经想好SwiftUI展示,那就直接做进界面的方法里好了,这里就是一个简单的网页请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
extension ImageNameDemoView {
func getImages(callback: @escaping ([String]) -> Void) {
let url = URL(string: "https://developer.apple.com/tutorials/data/documentation/appkit/nsimage/name.json")!
let task = URLSession.shared.dataTask(with: url) { d, _, _ in
guard let d = d else { return }
if let r = try? JSONSerialization.jsonObject(with: d, options: .mutableContainers) {
if let refs = (r as! [String: Any])["references"] {
var rl = [String]()
for v in (refs as! [String: Any]).values {
let n = (v as! [String: Any])["title"] as! String
if n.hasSuffix("Name") {
rl.append(n)
}
}
callback(rl)
}
}
}
task.resume()
}
}

我们要的数据全都在references键里,通过判断后缀是否有Name去除非图标的内容。

数据对应图标和尺寸

这里获取了需要的数据的数组,但这个数据只是属性名不是实际的值,所以还需要一个转化。

理论上来说这是个很简单的过程,想象中通过value(forKey:)就可以解决。但NSImage现在还不是正常实现的类,所以这个方法和同理的反射都不能用。

这里是可以OC去取值的,但实际上取巧的办法就可行:

1
2
3
4
5
6
7
extension ImageNameDemoView {
func getNSImageName(_ s: String) -> String {
var r = "NS\(s.prefix(1).capitalized)\(s.suffix(s.count - 1).prefix(s.count - 5))"
if r == NSImage.applicationIconName { r = "" }
return r
}
}

图标和对应的尺寸就简单了:

1
2
NSImage(named: name)
NSImage(named: name)?.size.debugDescription ?? ""

SwiftUI展示

大致构思一下界面,这里一行放十个图标,放一个按钮加载数据(onAppear也成),右击图标显示属性名和大小。

所以大致的框架就出来了:

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
struct ImageNameDemoView: View {

var columnNumber = 10
@State var images: [String] = []

var body: some View {
VStack {
ForEach(0 ..< images.count / columnNumber + 1, id: \.self) { y in
HStack(spacing: 5) {
ForEach(0 ..< self.columnNumber, id: \.self) { x in
VStack(spacing: 5) {
if x + y * self.columnNumber < self.images.count {
// replace Text with Image
Text("\(self.images[x + y * self.columnNumber])")
}
}.frame(width: 30, height: 30)
}
}
}
Button("Press") {
self.getImages() { l in
self.images = l
}
}
}
}
}

下面把中间的文字换成有右键菜单的图片:

1
2
3
4
5
6
Image(nsImage: NSImage(named: self.getNSImageName(self.images[x + y * self.columnNumber])) ?? NSImage())
.contextMenu {
Text("\(self.images[x + y * self.columnNumber])")
.font(.system(size: 16))
Text("\(NSImage(named: self.getNSImageName(self.images[x + y * self.columnNumber]))?.size.debugDescription ?? "")")
}

界面就快速的完成了,大概是一个这样的界面:

demo-image

其他

SwiftUI已经有了越来越好的开发体验,单数据源绑定的逻辑,完备的组件和方法,让界面的构建变得十分简单。

加上Widgets等内容的大力支持,地位可以想见会快速上升。

就我的体验而言,目前有一些问题还是需要有思想准备。Swiftui错误提示仍旧非常糟糕,界面中小的语法错误会导致整个构建时间耗时非常久,并最后返回,最外层View无法推断返回值类型这样的无用信息。

可能搜出来的答案很多是不合适的用法,也许我是个例,也许OC程序员写Swift都一样,看自动补全有好像能用的方法用了就行。例如文本框获取焦点,搜到的都是view.becomeFirstResponder()。IOS上奇怪的可用,MacOS上奇怪的不能用。翻翻文档才会发现调用view.window?.makeFirstResponder(view)才是标准的用法。

以及一些小问题,即使有NSViewRepresentable的万能后备,有时候还是会因为一些神奇的原因重写整个组件,例如目前List在MacOS上如果需要去掉分割线的背景需要重写整个List,甚至没有IOS上取巧的直接改写UITableView的办法。

但总体,惊喜是会比坑多的,建议一试。

🔲 ☆

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

即刻在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构建前端页面

前端框架越来越多,构建页面需要写的代码一天比一天少。绝大部分人都想过拖控件写前端、只写配置文件写前端、根据内容自动生成前端等。最近因为朋友的关注,无意中发现一个百度开源的通过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公里/小时移动,而哺乳动物需要做的只是走上去坐下。

☑️ ☆

浅析Pandownload作者可能面临的责任

本文不涉及百度的企业社会责任,只聊一聊软件作者可能涉及的刑事、民事责任,提供多一个角度。2020年4月初,扬州宝应网安对Pandownload的作者进行了刑事上的抓捕,而不是类似案件中常见的民事申请诉前证据保全。一来,这从法律上来说不可谓不是一个创新。二来,由于没有开源,本来想的从代码角度写cherryljr做了什么就变成了不应该干的事情。因为这两个理由我写了这篇文章,多一些了解也是多一些保护。

背景

扬州网警巡查执法通过新浪微博发布消息称四月初抓捕了Pandownload的作者,舆论发酵后删除,凤凰网对原微博进行了截图

今年 2 月,受害人刘某报案称其下载的“Pandownload”软件会在未授权的情况下,将自己百度网盘的数据共享出去,导致隐私照片和文件泄露。宝应县局网安民警立即开展案情分析研判,研习法律条文,解剖软件结构。面对满屏的数据,一盯就是几个小时。

经过梳理,警方发现该软件可以以非会员权限突破百度网盘官方设定,实现高速下载,系侵入、非法控制计算机信息系统的程序、工具,并且该软件的使用者达到数万人,致使北京百度网讯科技有限公司(“百度公司”)损失高达上千万元。

4月初,在掌握了充分证据后,宝应网安远赴广东,抓捕犯罪嫌疑人蔡某萌,当场查获电脑、手机等作案工具。

可以看出,该案已经作为刑事案件被立案侦查。目前,该项目与之后暂时出现的替代项目均关闭。

刑事责任

根据微博的内容,“该软件可以以非会员权限突破百度网盘官方设定,实现高速下载,系侵入、非法控制计算机信息系统的程序、工具”,即触犯非法获取计算机信息系统数据罪

不用讨论的是,若cherryljr的软件存在后门窃取用户数据或者破解手段为入侵百度服务器,则显然构成犯罪。有意思的是,曾经有一个收费提供百度云盘资源搜索的软件,法院判定软件上传使用者分享链接的行为构成窃取用户数据,判决见这里。这次的案件报案人也是采取该理由,但盘搜技术上需要上传分享链接用于搜索,而破解限速仅下载即可。所以除非cherryljr存在后门行为,不会因为报案人的理由构成犯罪。

值得讨论的是,若仅是截取了下载链接,通过三方工具下载,是否仍旧会构成犯罪。虽然涉案软件没有开源,但类似有很多项目,其实都是这样的操作。我们就假设,截取链接三方下载,是比较接近涉案软件的解决方案。

刑法条文

根据我国刑法第285条一、二款:

【非法侵入计算机信息系统罪】违反国家规定,侵入国家事务、国防建设、尖端科学技术领域的计算机信息系统的,处三年以下有期徒刑或者拘役。

【非法获取计算机信息系统数据、非法控制计算机信息系统罪】违反国家规定,侵入前款规定以外的计算机信息系统或者采用其他技术手段,获取该计算机信息系统中存储、处理或者传输的数据,或者对该计算机信息系统实施非法控制,情节严重的,处三年以下有期徒刑或者拘役,并处或者单处罚金;情节特别严重的,处三年以上七年以下有期徒刑,并处罚金。

侵犯法益

该罪保护的是社会法益,而非企业法益。非法获取计算机信息系统数据罪处于刑法第六章妨害社会管理秩序罪的第一节扰乱公共秩序罪,刑法体系而言,整章的犯罪均规制的是侵害社会法益的行为,包括该罪。若仅是企业的权益受到了损害,理应通过民事赔偿完成救济。

司法解释中也重点强调了公众隐私的保护,公众个人信息数量较小的非法获取也构成犯罪。

即使过度使用免费通道的行为侵犯了财产性利益,也应该认定盗窃罪。

犯罪行为

这里的技术问题可以做个类比,网盘就类似一家面包店,面包收钱,而试吃小样免费。面包店限制了一天只能买一次,所以不掏钱买面包就只有小样,吃不饱。于是聪明的人就多注册几个账号,去薅羊毛,也吃饱了。类比中,截取下载链接就是找到了小样窗口,三方多线程工具下载就是多账号薅羊毛。

需要注意到一个区别,本案并没有侵犯不公开数据。窃取阿里云备案完整信息案和本案不同。完整的备案信息含有个人隐私等不公开内容是内部文件,即使由于操作失误公网可以访问,恶意获取也属于非法。而本案中百度本就对所有用户提供免费的下载,数据也由其他用户上传。

涉案免费通道的使用边界不明,通过技术手段跑满免费带宽应属于合理使用。由于采取了更好的下载技术,同一链接,使用普通下载器和aria2下载,下载速度完全不同。同样的,aria2相对于百度提供的客户端而言,可以在同一链接的情况下获得更好的下载速度,使用更好的下载手段不应被认定为滥用。免费通道由于该技术满足了用户的下载需求,的确会产生了不符合自身商业意图的结果,公司可以从技术上进行限制,例如限制IP、下载模式识别、更高精度的限速模型、更低的限速、甚至关闭免费下载,来鼓励用户完成付费。

网盘产品主要是提供虚拟的存储空间,付费用户更快下载他人文件是附加功能,并非主要卖点及付费点。网盘归根到底是提供了云端的存储空间,用户可以云端备份文件、节省本地空间、同步手机相册等。网盘并非内容提供方,网络资源共享的途径多样,若网盘获取速度过慢,完全可以通过其他方式共享。优化下载文件速度的行为在另一方面也鼓励企业、个人将文件通过该网盘存储,难以判断商业上由此造成的影响。

情节严重

根据刑法条文可知,实施犯罪行为的情况下需情节严重才构成犯罪,情节严重的具体解释规定在相关司法解释中。

具有下列情形之一的,应当认定为刑法第二百八十五条第二款规定的“情节严重”:

(一)获取支付结算、证券交易、期货交易等网络金融服务的身份认证信息十组以上的;

(二)获取第(一)项以外的身份认证信息五百组以上的;

(三)非法控制计算机信息系统二十台以上的;

(四)违法所得五千元以上或者造成经济损失一万元以上的;

(五)其他情节严重的情形。

本案可能涉及的为第四条,而本案的软件的确存在可能的经济损失。

违法所得更难证明,但也有更多可以讲的内容。性质而言,本案软件的获利方式为捐赠,但的确存在捐赠才可以获得最新测试版的情况,实践中可能被认为是一种特定的商业模式。金额而言,会存在是否扣除成本的两种观点,一般作为定罪处罚标准时会采取扣除成本的观点,对此人民法院报也有过一篇专门的报道

是否构成财产犯罪

如果认定对网盘免费带宽的使用非法,本案的确可能存在非法占有财产性利益的情况。这个情况下仍需要注意,本案盗窃罪成立原因为达到了数额较大的起点,情节轻微,退赃退赔可不作为犯罪处理。另外,就该案的情节而言,违法行为系违法使用带宽,社会危害性较小。

民事责任

最基本的思路肯定是侵权责任纠纷,违法使用带宽,造成了额外的带宽费用,额外的带宽费用由违法行为导致,侵权人显然存在过错。

所以还有一个思路是不正当竞争纠纷。一,由于网站知名度等因素也会被认定为利益,认定营利性服务的经营者没有问题。涉案软件与网盘客户端的功能而言,使用涉案软件显然会导致不再使用网盘客户端,双方存在竞争关系。二,涉案软件必然影响网盘正常经营,研发时显然可以预见到。三,网盘旨在通过提供有区别的存储、下载等服务鼓励用户付费。涉案软件通过免费高速下载,让用户失去了付费的理由,破坏了网盘的商业模式。

不过就金额而言,带宽的费用损失难以精确的计算,侵权人违法所得也大都是小额捐赠,数额不大。

结语

舆论一直在发酵,一方面,破解软件的作者受到了某种意义的保护,另一方面,百度网盘的会员也安全了,真是奇妙的双赢。

☑️ ☆

一些有关幽默的理论

近期隔离在家,靠段子手获得了大量的快乐。一直在想这样一个问题,搞笑是一个幽默的人说了一件事,还是一个人说了一件幽默的事。之前看王自健的相关新闻,又一次看到对李诞的评论是,他是可以坐下来一页一页出段子的人,真是让人羡慕的能力。本文检索了一些幽默理论,结合脱口秀大会第二季呼兰决赛的稿子尝试一下分析和归类。

西方幽默理论

西方幽默理论有三个主要的理论流派,优越论、释放论和乖讹论。

优越论

这个理论认为,幽默是对某人或者某物的嘲笑,并由此获得优越感。这种方式包括表演者实施嘲笑,也包括表演者充当被嘲笑的对象。

萧伯纳一次在舞会上邀请一位大龄妇女,大龄妇女十分激动,询问自己是有什么过人之处。萧伯纳回应道,“我请你跳舞是因为这是一个慈善舞会,不是吗?”

仔细看来,甚至这个笑话可以说有侮辱妇女的成分,但的确是个好例子。

比如最近很火的小破站MV,自夸小队。其中其余三人顺利戴上了眼镜,中国boy不小心戳到眼睛。

比如卓别林、憨豆、周星驰,很多搞笑通过扮演被嘲笑的对象来达成。

释放论

这个理论认为,幽默是出于有压力的感情解除的确认。用弗洛伊德在《论幽默》一问里面的话说,就是感情消耗的节约。

金马奖上颁奖嘉宾调侃黄渤:你怎么穿个睡衣就来出席颁奖礼?

黄渤:因为这五年一直都在金马奖现场,这里已经像家一样。回到家里穿什么?回到家里一定要穿得舒服一点儿。

如果移除金马奖颁奖的紧张氛围,只是私下轻松的氛围下进行这段对话,就很难感受到搞笑。

乖讹论

这个理论认为,幽默是合理的期待落空。就是听众在笑话过程中产生了预期的结果,但结尾时获得了期待以外的结果,而这个意料之外的结果又异常和谐。

A: 你说我如果戴上分院帽会被分到哪个学院?以我的聪明应该会是斯莱特林吧。

B: 阿兹卡班。

这一理论特别常见,常见的抖机灵、搞笑配图、神最右大多是这一理论。

在文字横线处补写恰当的语句,使整段文字语义完整连贯。每处不超过15个字。

在为玫瑰剪枝的时候,不小心被刺刺到,一滴血珠渗出拇指,鲜红的血,颜色和盛放的红玫瑰一模一样。___?我在心里疑惑着。我一边吸着手指渗出的血珠,一边想着,……

答案:我缓缓打出一个问号

这需要大量的联想和创意,甚至铺垫两条故事线,好让最后的答案意料之外又情理之中。

例如思文奶奶的故事

举例分析

以下是脱口秀大会第二季呼兰决赛的稿子,不用冠军卡姆的稿子主要是因为卡姆的风格偏向于表演,很大部分的内容不在文字上,呼兰的脱口秀就更传统。

比赛到现在真的是弹尽粮绝,比赛三天前我的稿子还一塌糊涂。傍晚的时候给我爸发微信,我说,爸,我真写不出来,把我写的乱七八糟的发给他了。我爸他都没说安慰我一下,他回了我一下,我在外面应酬,等我回去我给你写一篇。我都没当回事儿,晚上十一点多,我爸发了一篇两千多字的稿子过来。我说哇,我在节目里面这么吐槽我爸妈,他还给我写稿子,这难道就是亲情的力量吗?结果一看那稿子,是我爸吐槽我爷爷的。

前面的铺垫产生了父子亲情的逾期,以为是个温暖的故事,没想到结尾是个一脉相承的吐槽父亲。另一个层面上来说,本来亲情和对儿子的支持是一个沉重的话题,最后的转折让紧张的气氛有了一个释放。

是吧,写的不咋地。好的话我就都用了嘛,我又不傻。

自己套入图方便、拿来主义的人设,可以算是一种自我贬低以产生搞笑的效果。

我爸写的脱口秀甚至有讽刺意味,可能是看了我的脱口秀,你这脱口秀怎么和你奶奶的鸡蛋酱似的,写脱口秀不舍得放梗呢。你这个脱口秀整麻烦了,找本字典里面放一个梗,天天能说脱口秀。

甚至用了夸张的手法,讽刺了自己脱口秀梗少的情况。

我小时候在东北,我爸有一次来一个英国的客户,随便吃了两顿饭之后英国人就飘了,觉得中国这酒也不好喝,中国人也不能喝,我这两天都没喝多。我爸想,这么合理的诉求,应该得到满足。就去跟他喝,临行前还问我说,就咱东北那些喝酒的绕口令,感情深一口闷,感情浅舔一舔,感情厚喝不够,感情铁喝吐血。用英语咋说呀?

最后一句用英语咋说呀,是意料之外不会去想到直接翻译的,但又很符合老父亲人设。

扩展阅读

1、弗洛伊德 《诙谐及其与无意识的关系》

2、格雷格·迪安 《手把手教你玩脱口秀》

3、《脱口秀大会》

🔲 ☆

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

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,再放映幻灯片。

☑️ ☆

博客代码框设置换行

博客里的代码框一直有个问题,每行的内容太长,代码框的宽度没法完整容纳一行。网上有过加横向滚动条、鼠标移上去就代码框自动加长这两种解决方案,但都不太美观。自动换行的方案符合大部分人的习惯,于是我研究了代码框的自动换行。本文设置了一个“行号和内容分开复制、代码自动换行并不影响自动高亮”的代码框,文中有可以用的代码以及用到属性的归纳。

基础的HTML代码

博客生成的代码框,格式经过简化就是如下的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<figure class="highlight javascript">
<table> <tbody> <tr>
<td class="gutter">
<pre>
<span class="line">0</span><br>
<span class="line">1</span><br>
<span class="line">2</span><br>
</pre>
</td>
<td class="code">
<pre>
<span class="line">"a "<span class="keyword">very</span>" very long line"</span><br>
<span class="line">a very very long line</span><br>
<span class="line">a very very long line</span><br>
</pre>
</td>
</tr> </tbody> </table>
</figure>

这些代码均为自动生成,也没有什么可以讨论的。代码高亮通过span实现;由于代码与行号生成在两个td中,所以复制代码的时候不会复制到行号。

换行的CSS设置

换行涉及到三个内容,具体实现的代码为:

1
2
3
4
figure .code span {
white-space: pre-wrap;
word-break: break-all;
}

其中分别的作用为:

  • white-space:设置元素中的空白符如何处理
  • overflow-wrap:设置完整单词的断开方式(未用到)
  • word-break:设置完整单词(包含汉语等的句子)的断开方式

white-space

空白符的处理简而言之是这样一张表:

属性多个换行空格和换行自动换行行末空格
normal保留一个保留一个移除
nowrap保留一个保留一个移除
pre保留保留保留
pre-wrap保留保留保留不换行
pre-line保留保留一个移除
break-spaces保留保留一个保留换行

我们的代码,对于多个换行需要全部保留,多个空格和换行需要全部保留,需要自动换行,行末空格保留但不需要因为空白字符换行。所以就white-space的属性可以选择pre-wrap

overflow-wrap

虽然空白符处理中我们设置了需要换行,但是过长的单词还是会导致行宽超出预设的宽度。

属性如何处理过长单词
normal保留
break-word断开

我们不希望因为有过长的单词导致出现横向滚动栏,所以这里可以选择break-word。但考虑到与word-break属性的作用重合,而且word-break属性更适合汉语的特别处理,所以就不再使用overflow-wrap

word-break

换行的设置也可以使用这个,这个属性可以将汉语整句视为一个单词不换行(直到有标点符号为止)。

属性英文处理中文处理
normal不断开单词断开句子
break-all断开单词断开句子
keep-all不断开单词不断开句子
break-word可能的话不断开单词断开句子

其中break-allbreak-word的区别为:若一行可以容纳一个单词但是无法容纳两个,break-all将断开第二个单词,break-word将把第二个单词放在第二行。

根据要求,我们就选用了break-all。这里其实和使用overflow-wrap: break-word是一样的效果。

换行的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
41
42
43
44
+function($) {
'use strict';

var CodeBlockResizer = function(elem) {
this.$codeBlocks = $(elem);
};

CodeBlockResizer.prototype = {
run: function() {
var self = this;
self.resize();
$(window).smartresize(function() {
self.resize();
});
},

resize: function() {
var self = this;
self.$codeBlocks.each(function() {
var $gutter = $(this).find('.gutter');
var $code = $(this).find('.code');

var codePaddings = $code.width() - $code.innerWidth();
var width = $(this).outerWidth() - $gutter.outerWidth() + codePaddings;

$code.css('width', width);
$code.children('pre').css('width', width);

for (var i = 0, m = $gutter.find('span').length; i < m; i++) {
$gutter.find('span').eq(i).height(
$code.find('span.line')[i].offsetHeight);
}
});
}
};

$(document).ready(function() {
$.fn.hasHorizontalScrollBar = function() {
return this.get(0).scrollWidth > this.innerWidth();
};
var resizer = new CodeBlockResizer('figure.highlight');
resizer.run();
});
}(jQuery);

这里与行号位置相关的部分就是29-32行的内容,根据offsetHeight调整height

仅是这样还是无法更改行号的位置,原因在于目前显示代码使用的都是span元素。我们若需要通过height调整行号所占的高度,应将其显示方式改为inline-block,所以增加如下CSS:

1
2
3
4
figure.highlight pre > .line {
display: inline-block;
line-height: 18px;
}

其中如果不设置display: inline-blockline-heightheight也将不相同,可能出现的小数将导致不必要的麻烦。

到这里就完成了全部关于代码换行的设置。

☑️ ☆

畅言更名相关商标权的探究

2019年6月6日,“畅言”官方公众号发布声明,“畅言”今后将会更名为“云评论”。出于好奇,我基于网络已经公开的信息,了解了一下这次商标侵权的内容。本文系本次事件的挖掘,并扩展联系了商标的相关内容。具体而言,文中简述了本次畅言商标侵权的背景,结合整理了相关的司法及商标局的记录,扩展罗列了相关法律法规。“畅言”变成“云评论”,只是改名的故事多没意思,如果发现是个商标侵权的故事,那就有很多值得一看的内容了。

这篇文章比较长,你可以挑着看,我也为此生成了目录。想听故事的话可以看“基本情况”这一节,想了解一些商标基础的话可以看“商标注册基础内容”这一节,只想看个结论的话“具体分析”这一节就够了。

基本情况

网上可以搜到相关的内容主要为:

  • 公众号声明内容
  • 两家公司背景
  • 声明后的变化
  • 诉讼开庭情况
  • 商标注册情况

公众号声明内容

2019年6月6日,“畅言”公众号(现已改名“云评论”)发布“关于侵犯科大讯飞股份有限公司注册商标权的致歉声明”。其中提到,“畅言”由于侵犯科大讯飞商标权,“畅言评论”将使用新名称“云评论”。

1
2
3
4
5
6
科大讯飞股份有限公司:

本公司因侵犯了科大讯飞股份有限公司持有的“畅言”注册商标权,致使科大讯飞股份有限公司之商标权受到损害,故本公司依据有关法律文书公开致歉,并承诺停止侵权,更换云评论官网(原搜狐畅言评论)(http://changyan.sohu.com 、http://changyan.kuaizhan.com)官网及管理后台出现的所有“畅言”的显示,该产品将使用新的名称“云评论”。同时呼吁业界尊重科大讯飞股份有限公司之合法权益,尊重商标权。

声明人:北京云站科技有限公司
2019年6月6日

两家公司背景

本声明涉及到两家公司,北京云站科技有限公司(下称“云站科技”)与科大讯飞股份有限公司(下称“科大讯飞”)。

云站科技旗下的“畅言”商标主要用于畅言评论系统,网络中可见的最早的记录为2012年11月27日,谷歌记录到了畅言评论发布了WordPress的插件(截图见这里)。而后畅言评论系统一直多见于各类论坛与博客,至今一直保持更新。由于目前国内各类评论插件都停止更新或不能使用,畅言评论系统已经成为很多人第三方评论系统的首选。

科大讯飞旗下的“畅言”商标主要用于畅言多媒体教学系统,网络中可见的最早记录为2012年3月29日,谷歌记录到了畅言智慧课堂教学系统的介绍文章(截图见这里)。而后网络上关于该服务的介绍较少,该情况可能因为该教学系统主要销售和宣传方式不是网络途径。目前科大讯飞旗下畅言相关的内容可在畅言云网站见到,更新了大量相关内容。

声明后的变化

当天,“云评论”官网上图标及各类提示都完成了替换。

不过云评论的二级域名还是保持了changyan.sohu.com。很大可能该域名不会弃用,“云评论”插件的载入也是依赖这一二级域名。一方面,这一二级域名显然并非商标权侵权行为。另一方面,启用另一域名yunpinglun.sohu.com作为一般用户的入口,让产品名称和二级域名统一并不会产生大量额外支出。

作为云评论的用户,我发现”云评论“广告栏位中竟然出现了侵权相对方科大讯飞的广告,我做了一个截图

作为用户不得不提的是,这两天的更新里针对广告部分做了优化,往常常规使用的CSS去广告已经失效。个人还是支持这一做法的,不付费用别人的产品,不屏蔽广告也是应有之义。不过如果广告实在太丑或者太打扰用户,通过一些手段改改大小或者换个地方合不合适呢?

诉讼开庭情况

通过公告可以看到商标权侵权的双方为,科大讯飞股份有限公司及北京云站科技有限公司。根据两公司的名字及案由,很容易的就能够搜索到公告信息(截图见这里)。

1
2
3
4
5
6
7
8
9
10
(2019)皖0191民初1247号 公告

我院定于2019年05月03日 09时00分在本院第十一法庭依法开庭审理被告北京云站科技有限公司与原告科大讯飞股份有限公司商标权权属、侵权纠纷一案。

案号:(2019)皖0191民初1247号
承办人:赵晨

特此公告

二〇一九年二月二十日

据此可以看到该案已经进入了司法程序,可能已经产生生效的法律文书。为此,我尝试搜索了中国裁判文书网(搜索结果见这里),截止到2019年6月10日没有找到相应的判决。考虑到调解书和部分判决书不上网以及判决书需要一定时间才会上网,这一结果也在情理之中。

不过不得不吐槽一下,查询过程当中裁判文书网大部分时间都处于“系统繁忙,请您稍后再试”的状态,搜索实在难以完成。当我看到有一个注册界面的时候,我朴素的以为由于我不是注册用户,搜索享受着爬虫待遇。结果不仅注册无法完成,“用户名查重失败:Index was outside the bounds of the array.”;而且找朋友借了一个账号后搜索速度完全没有好转。

通过开庭的信息,可以确定的是云站科技与科大讯飞之间确实存在商标权权属争议及侵权纠纷,该纠纷最后以云站科技停止使用“畅言”商标结束。

商标注册情况

国家知识产权局商标局有下设中国商标网,通过其中的综合搜索,搜索商标名称为“畅言”的商标,可以找到“畅言”相关的34个商标。

其中本次的双方注册情况为:

  • 科大讯飞股份有限公司于2000年01月24日申请了名称为“畅言”的9类商标
  • 科大讯飞股份有限公司于2008年05月14日至2011年05月31日申请了名称为“畅言”的9、16、28、41、42类商标
  • 科大讯飞股份有限公司于2018年12月05日申请了名称为“畅言”的9、16、41、42类商标
  • 云站科技科技有限公司于2019年04月09日申请了名称为“畅言云评”的9、38、42类商标

具体内容我做了一张表,表的内容见这里

商标注册内容

这里涉及到几类商标,具体的内容有《类似商品和服务区分表》具体作了解释,简单的来说:

  • 9:
    • 0901: 电子计算机机器外部设备
    • 0907: 通讯导航设备
    • 0908: 音响设备
    • 0923: 电影片,已曝光材料
  • 38:
    • 3801: 进行播放无线电或电视节目的服务
    • 3802: 通讯服务
  • 42:
    • 4220: 计算机编程及相关服务

考虑到2019年2月20日已作出开庭公告,2019年4月9日云站科技申请商标的行为在后,可以推测云站科技认为其在9、38、42类享有商标权,具体的小类为上述几类。

其中几个小类的商标权科大讯飞并未申请,分别为0907、0923、3801、3802、4220。科大讯飞有在先申请的为0901与0908。

就商品/服务项目而言,类似群重合的部分注册情况为:

  • 云站科技:
    • 0901:可下载的音乐文件、笔记本电脑、可下载的影像文件、计算机软件(已录制)、计算机程序(可下载软件)、电子出版物(可下载)、可下载的计算机游戏软件
    • 0908:便携式媒体播放器
  • 科大讯飞:
    • 0901:计算机存储装置、计算机、磁盘、可下载的影像文件、可下载的计算机应用软件、可下载的手机应用软件、电子出版物(可下载)、计算机软件、计算机程序、读出器、数据处理设备、条码读出器、文字处理机、信息处理机
    • 0908:录音装置、带有图书的电子发声装置、扬声器音箱、个人用立体声装置

云站科技注册商标

中国商标网的搜索结果中可以看到云站科技注册的商标,通过网上简单的字体比对可以发现云站科技注册的“畅言云评”图标构成十分简单。图标为800x800的图片,内容为“畅言云评”四个字,字体为宋体,没有特别的设计或装饰。

我将“畅言云评”的商标与宋体的这四个字做了一个对比,对比图见这里

其中宋体是一个有意思的字体,宋体一直给人一种默认自带的感觉,但其实他的著作权归属于北京中易中标电子信息技术有限公司(下称“中易中标”)。我们常见的微软自带宋体背后,中易中标与微软签订过《字体许可协议》,微软获得了中易中标的许可。但由于Windows 98及之后的系统不属于授权范围,中易中标于2007年起诉微软,产生了(2007)一中民初字第5362号的著作权纠纷,具体内容即为宋体及黑体的著作权侵权。

可见宋体的商用需要获得中易中标的授权,未授权的使用属于侵犯著作权的行为。不过中易中标可见的诉讼记录中除了微软的诉讼未发现对任何一家公司的判决,可能其怠于行使民事权利甚至默认该类侵权行为,当然考虑到该公司背景也完全可能通过其他途径实现民事权利。

商标注册相关内容

注册流程

商标注册有一系列流程,具体而言包含,向商标局提交申请书、形式审查、实质审查、初步审定公告、注册公告、注册。每一步骤的期限以及具体要求法律都有明确的规定,整体流程完成后即完成注册。

整个过程还是很复杂的,有兴趣的这里提供一张流程图(点击这里查看)。

可以看到整个注册过程就是审查与公告,经过了法定的公告期没有成立的异议则申请人可以获得申请商标的商标权。

商标大类与小类

本文中提到了商标的大类与小类,标准来讲,商标的大类即国际分类,商标的小类即类似群。从上面的申请表国标网显示的内容可能会发现,商标申请仅需要提供类别和商品/服务项目,不需要填写类似群。商标/服务项目是类似群更下层的分类,以笔记本电脑为例,其大类(国际分类)为9,小类(类似群)为0901,商标/服务项目是笔记本电脑(090103)。

商标注册与保护的联系

那么商标权是怎么保护的呢,大类相同就可保护,还是需要商标/服务项目相同才可保护?结论是很有意思的,保护和分类并没有必然的关系。

根据《最高人民法院知识产权案件年度报告(2011)》,商标保护的认定不能根据物理属性确认,而应判断是否能共存。若侵权商标与原商标无法共存,则应认定侵权商标涉及的商品也在原商标的保护范围内。“最高人民法院审查认为:商标法设置商品类似关系,是因为商标主要是按商品类别进行注册、管理和保护。在商标授权确权和侵权判定过程中,进行商标法意义上相关商品是否类似的判断,并非作相关商品物理属性的比较,而是主要考虑商标能否共存或者决定商标保护范围的大小。”

为此,最高法以鞋商标侵权服装商标为例,具体阐述了该问题。“避免来源混淆是商品类似关系判断时要坚持的一项基本原则。本案中,争议商标指定使用的商品为鞋和靴,引证商标核定使用的商品是服装等。虽然两者在具体的原料、用途等方面具有一些差别,但是两者的消费对象是相同的,而且在目前的商业环境下,一个厂商同时生产服装和鞋类产品,服装和鞋通过同一渠道销售,比如同一专卖店、专柜销售的情形较为多见。同时,争议商标与引证商标中的“鸟图形”虽然在细部上略有差异,但两者基本形态相同,且根据查明的事实,引证商标通过使用具有较高的知名度。在这种情况下,如果两商标在服装和鞋类商品上共存,容易使相关公众认为两商品是同一主体提供的,或者其提供者之间存在特定联系。因此,争议商标与引证商标构成类似商品上的近似商标。”

关于《类似商品和服务区分表》,也就是其中大类、小类、商标/服务项目的分类,最高法院给出了这样一个态度。“最高人民法院认为,《区分表》可以作为判断类似商品或者服务的参考,但不能机械、简单地以《区分表》为依据或标准,而应当更多地考虑实际因素,结合个案的情况进行认定。”

所以一般同类的商品属于侵权,但实际上也完全可能存在跨类的侵权,例如最高法提到的鞋与服装的跨类侵权。

商标侵权相关法律

《中华人民共和国商标法》

第十三条 为相关公众所熟知的商标,持有人认为其权利受到侵害时,可以依照本法规定请求驰名商标保护。

第四十九条 注册商标成为其核定使用的商品的通用名称或者没有正当理由连续三年不使用的,任何单位或者个人可以向商标局申请撤销该注册商标。商标局应当自收到申请之日起九个月内做出决定。有特殊情况需要延长的,经国务院工商行政管理部门批准,可以延长三个月。

第五十七条 有下列行为之一的,均属侵犯注册商标专用权:
  (一)未经商标注册人的许可,在同一种商品上使用与其注册商标相同的商标的;
  (二)未经商标注册人的许可,在同一种商品上使用与其注册商标近似的商标,或者在类似商品上使用与其注册商标相同或者近似的商标,容易导致混淆的;
  (三)销售侵犯注册商标专用权的商品的;
  (四)伪造、擅自制造他人注册商标标识或者销售伪造、擅自制造的注册商标标识的;
  (五)未经商标注册人同意,更换其注册商标并将该更换商标的商品又投入市场的;
  (六)故意为侵犯他人商标专用权行为提供便利条件,帮助他人实施侵犯商标专用权行为的;
  (七)给他人的注册商标专用权造成其他损害的。

第五十九条 商标注册人申请商标注册前,他人已经在同一种商品或者类似商品上先于商标注册人使用与注册商标相同或者近似并有一定影响的商标的,注册商标专用权人无权禁止该使用人在原使用范围内继续使用该商标,但可以要求其附加适当区别标识。

第六十三条 侵犯商标专用权的赔偿数额,按照权利人因被侵权所受到的实际损失确定;实际损失难以确定的,可以按照侵权人因侵权所获得的利益确定;权利人的损失或者侵权人获得的利益难以确定的,参照该商标许可使用费的倍数合理确定。对恶意侵犯商标专用权,情节严重的,可以在按照上述方法确定数额的一倍以上五倍以下确定赔偿数额。赔偿数额应当包括权利人为制止侵权行为所支付的合理开支。

《最高人民法院关于审理商标民事纠纷案件适用法律若干问题的解释》

第十一条 商标法第五十二条第(一)项规定的类似商品,是指在功能、用途、生产部门、销售渠道、消费对象等方面相同,或者相关公众一般认为其存在特定联系、容易造成混淆的商品。
类似服务,是指在服务的目的、内容、方式、对象等方面相同,或者相关公众一般认为存在特定联系、容易造成混淆的服务。
商品与服务类似,是指商品和服务之间存在特定联系,容易使相关公众混淆。

具体分析

是否实际侵权

本案双方的商标不同,属于近似商标,应适用《商标法》第五十七条第二款认定侵权,“未经商标注册人的许可,在同一种商品上使用与其注册商标近似的商标,或者在类似商品上使用与其注册商标相同或者近似的商标,容易导致混淆的”。也就是说,一方面,侵权的前提是商品属于同一种类。另一方面,使用近似商标并不一定承担侵权责任,还必须判断是否会使消费者产生混淆。

云站科技的“畅言评论”自2012年起提供的服务均为社会化评论系统,科大讯飞的“畅言教育”自2012年起提供的服务均为教育辅助系统。“畅言评论”为评论插件,是完整网页或者应用的可选组成部分,“畅言教育”为教学辅助应用,是完整的应用,难以认定为同一种商品。侵权的前提无法达到,故难以认定为侵权。从混淆的角度来说,“畅言评论”提供的是评论服务,“畅言教育”提供的是教育资源与数据,内容上无法构成混淆。“畅言评论”的消费者是需要评论功能的开发者,“畅言教育”的消费者是需要教育资源与数据的教育机构或开发者,目标消费者人群不同,难以产生混淆的效果。

故从前提或混淆的效果,本案均难以认定为商标权侵权。

可能的结果

关于更名的结果已经不用说了,云站科技选择不再使用“畅言”商标。

就用户的吸引与流失,老用户而言,可以想象没什么影响,改名后第一次点进后台可能会有些惊讶。而新用户,身边使用过”畅言“评论插件的朋友,大都是通过旧的文章介绍了解到他。如果无法再通过搜索”畅言“找到该插件,势必会影响新用户的产生。

好在目前,我通过常用的几个搜索引擎进行了搜索,云站科技的网站目前仍旧排在“畅言”搜索结果的首位。但值得注意的是,”畅言“教育平台的搜索结果,虽在前两位的”畅言“评论系统后面,但也占据了后面的四个到五个位置。随着更名后的时间推移以及科大讯飞可能的加大”畅言“教育平台的宣传力度,也许不久之后”畅言“评论系统将被挤出搜索的第一页,从而无法通过积累的“畅言”口碑获得用户。

另一个值得一提的是,科大讯飞在云站科技投放了广告,科大讯飞在使用云评论插件的广告栏位。我也好奇这个广告栏位的使用,是云站科技给出的补偿,还是科大讯飞付钱买商标的方式呢?单就从云站科技实际侵权的可能性较小的角度来看,这一点云站科技的常法律师出具的法律意见书中一定会提到,很大可能使用广告栏会是付费的。

国内云评论服务的提供商越来越少,起码“云评论”还是个完备可用的选择。“云评论”评论系统这次更名,失去了“畅言”的商标,避免了一个麻烦,可能获得了一笔不知数额的广告费。总有一种把自家牌匾卖了换钱的感觉,揭得开锅谁卖牌匾,和他死磕到底呗。也不知道现在“云评论”的经营情况如何,还能不能继续下去。

🔲 ☆

畅言PC版emoji显示

畅言目前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")

🔲 ☆

畅言评论插件优化

搜狐旗下有一款评论插件,畅言,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文件压缩好每次载入即可。

❌