前言

为了降低门槛,维基媒体基金会一直在推进『所见即所得』编辑器的发展;然而时至今日,MediaWiki 的可视化编辑器依旧功能孱弱,许多老编者依旧更青睐于源代码编辑。

造成这一局面除了是由于路径依赖以外,各种模板不方便进行可视化操作也是成因之一。各种丰富的模板缔造了繁荣的 wiki 生态,但它们形式上极其自由,为将模板引入可视化系统造成不便。

为此,我们亟需对模板的编写添加更多的限制,以便在可视化系统中查找、选择与引用模板,以及在为模板添加参数时生成对人类友好的表单。

我的设想对模板系统的改变无异于重构,对旧体系下的模板不兼容,很难实现,所以放在博客的“虚构”分类中,希望能博君一笑罢。

模板编辑模式

除了“前言”中提到的问题外,MediaWiki 模板中似乎已经图灵完备的解析器函数也需要改进。

MediaWiki是一套历史悠久的系统,当初为了在模板中实现一些简单逻辑的解析器函数愈发臃肿。所以我们新的模板是基于某个现代的编程语言的,鉴于MediaWiki本身依靠网页呈现,以及 JavaScript ES6 中“模板字符串”的新特性,我们决定基于JavaScript 来构建我们的模板系统。

创建新模板后,页面上将出现两个编辑区域,其中左边用来编辑模板的主体部分,右边用来编辑模板的逻辑部分。当使用模板时,网页将会把这两部分拼合成一个函数,并在页面对应位置引用这个函数。模板函数将会类似这个样子:

1
2
3
4
function <模板名> () {
<模板逻辑>
return `<模板主体>`
}

我们来举一个简单的例子来说明模板的编辑方式:

模板:提示(旧版)

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
<div style="
margin: 0 auto;
border-radius: 5px;
padding: 10px;
border: 1px solid #aaa;
margin-bottom: 10px;
width: {{{width|{{{宽度|100%}}}}}};
max-width: 100%;
text-align: left;
background:{{#switch:{{{1|{{{type|{{{类型|}}}}}}}}}
|绿色
|成功=rgba(149, 255, 128, 0.05)
|蓝色
|消息
|提示=rgba(128, 212, 255, 0.05)
|黄色
|注意
|警告=rgba(255, 234, 128, 0.05)
|红色
|危险
|错误=rgba(255, 128, 128, 0.05)
|rgba(255, 255, 255, 0.05)
}};
border-left: 6px solid {{#switch:{{{1|{{{type|{{{类型|}}}}}}}}}
|绿色
|成功=rgba(149, 255, 128, 1)
|蓝色
|消息
|提示=rgba(128, 212, 255, 1)
|黄色
|注意
|警告=rgba(255, 234, 128, 1)
|红色
|危险
|错误=rgba(255, 128, 128, 1)
|rgba(255, 255, 255, 1)
}};
{{{style|}}}
">
{{#if:{{{2|{{{title|{{{标题|}}}}}}}}}
|<span style="
text-shadow:1px 1px #000;
color: {{#switch:
{{{1|{{{type|{{{类型|}}}}}}}}}
|绿色
|成功=rgba(149, 255, 128, 1)
|蓝色
|消息
|提示=rgba(128, 212, 255, 1)
|黄色
|注意
|警告=rgba(255, 234, 128, 1)
|红色
|危险
|错误=rgba(255, 128, 128, 1)
|rgba(255, 255, 255, 1)
}}
">
'''{{{2|{{{title|{{{标题|}}}}}}}}}'''
</span>
<br>}}

{{#if:{{{3|{{{text|{{{内容|}}}}}}}}}
|<span style="
{{#ifeq:{{{居中|}}}
|是
|display: block;text-align: center;
}}
{{{text-style|{{{文字样式|}}}}}}
">
{{{3|{{{text|{{{内容|}}}}}}}}}
</span>
}}
</div>

模板:提示(新版-主体部分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div style="
margin: 0 auto;
border-radius: 5px;
padding: 10px;
border: 1px solid #aaa;
margin-bottom: 10px;
width: ${width};
max-width: 100%;
text-align: left;
background: ${body_bg};
border-left: 6px solid ${border_left_color};
${style}
">
${title ? `<span test-shadow:1px 1px #000; color:${title_color}>${title}<span>`:{}}
${text_content}
</div>

模板:提示(新版-逻辑部分)

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
// 模板的配置
// 将会根据配置自动定义 模板的参数 和 模板页展示的内容
config.doc = 模板.模板文档()
config.input = {
$1: {
name: {zh: "类型", en: "type"},
type: "select",
option: ["成功", "提示", "警告", "危险"] // 提供的选项
},
$2: {
name: {zh: "标题", en: "title"},
type: "text",
},
$3: {
name: {zh: "内容", en: "text"},
type: "textarea"
},
width: {
name: {zh: "宽度", en: "width"},
type: "length"
},
text_style: {
type: "text"
}
居中: {
type: "radio"
option: ["是", "否"]
}
}

// 逻辑
switch(type) {
case "绿色":
case "成功":
body_bg = "rgba(149, 255, 128, 0.05)";
border_left_color = "rgba(149, 255, 128, 1)";
title_color = "rgba(149, 255, 128, 1)";
break;
case "蓝色":
case "消息":
case "提示":
body_bg = "rgba(128, 212, 255, 0.05)";
border_left_color = "(149, 255, 128, 1)";
title_color = "rgba(149, 255, 128, 1)";
break;
case "黄色":
case "注意":
case "警告":
body_bg = "rgba(255, 234, 128, 0.05)";
border_left_color = "rgba(128, 212, 255, 1)";
title_color = "rgba(128, 212, 255, 1)";
break;
case "红色":
case "危险":
case "错误":
body_bg = "rgba(255, 128, 128, 0.05)";
border_left_color = "rgba(255, 128, 128, 1)";
title_color = "rgba(255, 234, 128, 1)";
break;
default:
body_bg = "(255, 255, 255, 0.05)";
border_left_color = "rgba(255, 255, 255, 1)";
title_color = "rgba(255, 255, 255, 1)";
}

if (text) {
if (居中 == "是") {
text_content = `<span style="display: block;text-align: center;${text_style}">${text}</span>`
} else if (居中 == "否") {
text_content = `<span style="${text_style}">${text}</span>`
}
}

模板的使用

在编辑模板的逻辑部分时,可以使用 template.<模板名>()模板.<模板名>() 引用模板。

在其他地方,MediaWiki的语法已经足够完善了,但我也设计了一种函数风格的模板引用方式。具体地说,类似这样:

1
2
3
4
5
6
7
8
9
(<模板名>(
"一号参数",
"二号参数",
"三号参数",
{
参数名1: "值一",
参数名2: "值二"
}
))

其中传入命名参数的那个对象必须置于最后一位。以上写法和下面等价:

1
2
3
4
5
6
7
{{<模板名>
|一号参数
|二号参数
|三号参数
|参数名1 = 值一
|参数名2 = 值二
}}

模板的配置项

在编辑模板的逻辑部分时,需要定义模板的配置项(也就是 config 变量)。目前 config 中仅有 config.input 和 config.doc 两部分。

config.doc 定义了模板页的展示内容。当 config.doc 不存在时,模板页将展示一个默认状态下的模板,用户可以点击它,然后在弹出的表单中配置模板的各项传参(但不会保存),就像在可视化编辑中使用模板那样。用户可以通过这样的方式学习模板的用法。

config.input 定义了可向模板传入的参数,其中“$ + 数字”组成的参数代表数字参数。必须指定所有参数的类型,以便于在可视化编辑器中生成用于配置模板的表单。

为了便于记忆,参数的类型和其他属性的命名大部分来自 html 中的<input>标签,详情如下:

变量类型 备注
text 输入框
textarea 多行输入框
radio 单选按钮
checkbox 复选框,选中的内容将合并成一个数组
color 拾色器
date 日期,将会把时间戳赋值给参数
datetime 日期和时间
time 时间
number 数字,将提供“+1”和“-1”两个按钮
url 链接
image 图像,此处特指图像链接
length 长度,数字和单位拼接成的参数
range 滑块,将返回一个不精确的数字

以下是参数可选的其他属性:

参数属性 类型 备注
name 字符串、对象 规定了变量的别称,其中键为语言代码,会根据用户选择的语言命名对应的配置项。
type 字符串 规定了变量类型
option 数组 规定了可选择的项;当数组中的某一项是变量类型之一时,代表用户选择此项时可以自定义选项内容。
checked 基本类型、数组 规定了默认选定的值。
max 数字 规定了参数的上限
min 数字 规定了参数的下限
maxlength 数字 规定了参数的最大长度
placeholder 字符串 当参数类型为输入框时,规定了提示文本
rows 数字 当参数类型为多行输入框时,规定了输入框可见的行数
required 布尔值 规定是否为必填项