diff --git a/app/app.vue b/app/app.vue index 3439d66..1907ded 100644 --- a/app/app.vue +++ b/app/app.vue @@ -4,3 +4,9 @@ + + \ No newline at end of file diff --git a/app/assets/scss/common.scss b/app/assets/scss/common.scss new file mode 100644 index 0000000..d5e9838 --- /dev/null +++ b/app/assets/scss/common.scss @@ -0,0 +1,2 @@ +@use "./markdown/reset.scss"; +@use "./markdown/green.scss"; \ No newline at end of file diff --git a/app/assets/scss/global.scss b/app/assets/scss/global.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/scss/markdown/green.scss b/app/assets/scss/markdown/green.scss new file mode 100644 index 0000000..55b310b --- /dev/null +++ b/app/assets/scss/markdown/green.scss @@ -0,0 +1,306 @@ + +@mixin chinese() { + &.chinese { + text-indent: 1.5em; + font-weight: 300; + h1, + h2, + h3, + h4, + h5, + h6, + ol, + ul, + blockquote, + details, + summary, + pre, + .tabs { + text-indent: 0; + } + } +} +.markdown-body { + --color-base: #ef4444; + --markdown-bg: white; + --color-bg: #ff47479c; + --color-light: #ef44441a; + --color-extra: rgba(239, 68, 68, 0.3); + --color-more: rgba(239, 68, 68, 0.4); +} + +.markdown-body.green { + background-color: var(--markdown-bg); + @apply p-3 lg:p-6; + + strong { + &::before{ + content: "『"; + } + &::after{ + content: "』"; + } + // color: #ff4d20; + } + + /* 块/段落引用 */ + // blockquote { + // position: relative; + // // color: #555; + // font-weight: 400; + // border-left: 6px solid var(--color-base); + // padding-left: 1em; + // margin-left: 0; + // padding: 1em; + // background-color: var(--color-light); + // } + + blockquote { + position: relative; + z-index: 600; + padding: 20px 20px 15px 20px; + line-height: 1.4 !important; + background-color: rgba(239, 68, 68, 0.06); + border-radius: .4em; + > * { + position: relative; + &:first-child:before { + content: '\201C'; + color: var(--color-light); + font-size: 6.5em; + font-weight: 700; + transform: rotate(15deg) translateX(-10px); + opacity: 1; + position: absolute; + top: -.4em; + left: -.2em; + text-shadow: none; + z-index: -10; + } + } + } + + .tabs{ + margin-top: 0; + margin-bottom: 1em; + } + + /* 让链接在 hover 状态下显示下划线 */ + // a { + // color: var(--color-base); + // text-decoration: none; + // &:hover { + // text-decoration: underline; + // } + // } + a { + position: relative; + z-index: 10; + transition: color 0.3s linear; + cursor: pointer; + font-weight: bolder; + // text-decoration: underline #c7254e; + text-decoration: none; + color: var(--color-base); + border-bottom: 1px solid currentColor; + padding: 0 4px; + &[data-footnote-backref], + &[data-footnote-ref] { + // text-decoration: none; + border: none; + &:hover { + background: none; + animation: none; + } + } + &:hover { + content: ''; + // text-decoration: none; + border: none; + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 4'%3E%3Cpath fill='none' stroke='%23ff4d20' d='M0 3.5c5 0 5-3 10-3s5 3 10 3 5-3 10-3 5 3 10 3'/%3E%3C/svg%3E") + repeat-x 0 100%; + background-size: 20px auto; + animation: waveMove 1s infinite linear; + } + @keyframes waveMove { + 0% { + background-position: 0 100%; + } + 100% { + background-position: -20px 100%; + } + } + } + + // 由prism.scss处理了 + pre { + background: var(--prism-background); + font-size: var(--prism-font-size); + // /* border: 1px solid #ddd; */ + // padding: 1em 1.5em; + display: block; + -webkit-overflow-scrolling: touch; + } + + pre, + pre code { + font-family: var(--prism-font-family); + } + + // /* 底部印刷体、版本等标记 */ + small, + /* 图片说明 */ + figcaption { + font-size: 0.75em; + color: #888; + } + // .markdown-body { + legend { + color: #000; + font-weight: inherit; + } + caption { + color: #000; + font-weight: inherit; + } + del { + text-decoration: line-through var(--color-base) 2px; + } + code { + color: rgb(45, 55, 72); + background-color: rgba(160,174,192,0.25); + font-family: inherit; + font-size: 1em; + // color: #ffffff; + // background-color: #ff4c209c; + border-radius: 4px; + padding: 1px 6px; + // font-size: 0.875em; + // font-size: 1.0769em; + // position: relative; + // top: 0.1em; + margin: 0 2px; + vertical-align: bottom; + } + pre { + code { + padding: 0; + font-size: inherit; + font-weight: inherit; + color: inherit; + white-space: pre; + background-color: transparent; + vertical-align: baseline; + border-radius: 0; + margin: 0; + } + } + table { + width: 100%; + display: table; + border: 1px solid var(--color-light); + tr:hover { + background: var(--color-light); + } + &:hover{ + border: 1px solid var(--color-extra); + th { + border-right: 1px solid var(--color-extra); + background: var(--color-extra); + border-bottom: 1px solid var(--color-extra); + } + td { + border-right: 1px solid var(--color-extra); + } + caption { + border-right: 1px solid var(--color-extra); + } + thead { + th { + background: var(--color-extra); + } + } + } + // table-layout: fixed; + th { + border-right: 1px solid var(--color-light); + padding: 0.5em 1em; + background: var(--color-light); + border-bottom: 1px solid var(--color-light); + } + td { + border-right: 1px solid var(--color-light); + padding: 0.5em 1em; + } + caption { + border-right: 1px solid var(--color-light); + padding: 0.5em 1em; + border-bottom: none; + } + thead { + th { + background: var(--color-extra); + } + } + } + h1, + h2, + h3, + h4, + h5, + h6 { + margin-top: 1.2em; + margin-bottom: 0.6em; + line-height: 1.35; + position: relative; + } + h1 { + font-size: 1.8em; + } + h2 { + font-size: 1.6em; + } + h3 { + font-size: 1.4em; + } + h4 { + font-size: 1.2em; + } + h5 { + font-size: 1em; + } + h6 { + font-size: 1em; + } + + ::-webkit-calendar-picker-indicator { + filter: invert(50%); + } + em { + // color: #000; + font-weight: inherit; + position: relative; + &:after { + position: absolute; + top: 0.65em; + left: 0; + width: 100%; + overflow: hidden; + white-space: nowrap; + pointer-events: none; + color: var(--color-base); + content: '・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・'; + } + } + ul.toc { + li { + list-style-type: none; + a { + text-decoration: none; + border: 0; + list-style-type: none; + } + } + } + @include chinese; +} diff --git a/app/assets/scss/markdown/reset.scss b/app/assets/scss/markdown/reset.scss new file mode 100644 index 0000000..85f7ac0 --- /dev/null +++ b/app/assets/scss/markdown/reset.scss @@ -0,0 +1,454 @@ + +// https://verytoolz.com/blog/03bfb3598f/ +.markdown-body { + --color-fg-default: #24292f; // 文本色-默认 + --color-fg-muted: #57606a; // 文本色-柔和 + --color-fg-subtle: #6e7781; // 文本色-微妙 + --color-canvas-default: #ffffff; // 底色-默认 + --color-canvas-subtle: #f6f8fa; // 底色-微妙 + --color-border-default: #d0d7de; // 边框色-默认 + --color-border-muted: hsla(210, 18%, 87%, 1); // 边框色-柔和 + --color-neutral-muted: rgba(175, 184, 193, 0.2); // 边框色-中性 + --color-accent-fg: #0969da; // 文本强调色 + --color-accent-emphasis: #0969da; // 背景强调色 + --color-attention-subtle: #fff8c5; // 背景注意色 + --color-danger-fg: #cf222e; // 文本危险色 + --color-mark-default: rgb(255, 255, 0); // mark 默认色 + --color-mark-fg: rgb(255, 187, 0); // mark 强调色 +} + +.markdown-body { + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + text-rendering: optimizelegibility; + margin: 0; + word-wrap: break-word; + color: var(--color-fg-muted); + &::before { + display: table; + content: ''; + } + &::after { + display: table; + clear: both; + content: ''; + } + > *:first-child { + margin-top: 0 !important; + } + > *:last-child { + margin-bottom: 0 !important; + } + p, + blockquote, + ul, + ol, + dl, + table, + hr, + form, + pre, + details { + margin-top: 0; + margin-bottom: 1em; + } + blockquote { + & > :first-child { + margin-top: 0; + } + & > :last-child { + margin-bottom: 0; + } + } + /* 统一显示成块状元素 */ + details, + figcaption, + figure { + display: block; + } + /* HTML5 媒体文件跟 img 保持一致 */ + audio, + canvas, + video { + display: inline-block; + } + /* 按钮内部间距统一 */ + button::-moz-focus-inner, + input::-moz-focus-inner { + padding: 0; + border: 0; + } + /* 定义元素显示为斜体 */ + dfn { + font-style: italic; + } + /* 去掉各Table cell 的边距并让其边重合 */ + table { + border-collapse: collapse; + border-spacing: 0; + display: block; + width: max-content; + max-width: 100%; + overflow: auto; + } + /* 可拖动文件添加拖动手势 */ + [draggable] { + cursor: move; + } + /* 加粗元素 */ + b, + strong { + font-weight: var(--base-text-weight-semibold, 600); + } + /* 缩写元素样式统一 */ + abbr, + acronym { + border-bottom: none; + font-variant: normal; + text-decoration: underline dotted; + } + /* 添加鼠标问号,进一步确保应用的语义是正确的(要知道,交互他们也有洁癖,如果你不去掉,那得多花点口舌) */ + abbr { + cursor: help; + } + /* 一致的 del 样式 */ + del { + text-decoration: line-through; + } + /* a标签去除下划线 */ + a { + text-decoration: none; + &:not([href]) { + color: inherit; + text-decoration: none; + } + &:hover { + text-decoration: underline; + } + } + /* 默认不显示下划线,保持页面简洁 */ + ins { + text-decoration: none; + } + /* 专名号:虽然 u 已经重回 html5 Draft,但在所有浏览器中都是可以使用的, + * 要做到更好,向后兼容的话,添加 class="typo-u" 来显示专名号 + * 关于 标签:http://www.whatwg.org/specs/web-apps/current-work/multipage/text-level-semantics.html#the-u-element + * 被放弃的是 4,之前一直搞错 http://www.w3.org/TR/html401/appendix/changes.html#idx-deprecated + * 一篇关于 标签的很好文章:http://html5doctor.com/u-element/ + */ + u, + .typo-u { + text-decoration: underline; + } + /* 隐藏指定元素 */ + [hidden] { + display: none !important; + } + /* 伸缩框显示为列表元素 */ + summary { + display: list-item; + } + q:before, + q:after { + content: ''; + } + caption, + th { + text-align: left; + } + caption[align='center'], + th[align='center'] { + text-align: center; + } + address, + caption, + cite, + em, + th, + var { + font-weight: 400; + } + /* 标记,类似于手写的荧光笔的作用 */ + mark { + background: var(--color-mark-default); + // background: #fffdd1; + // border-bottom: 1px solid #ffedce; + padding: 2px; + + &.active{ + background: var(--color-mark-fg); + } + // margin: 0 5px; + } + /* 统一h1元素的间隔和字体大小 */ + h1 { + margin: 0.67em 0; + font-weight: var(--base-text-weight-semibold, 600); + font-size: 2em; + } + /* small字体缩小 */ + small { + font-size: 90%; + } + /* 上下标显示 */ + sub, + sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + sub a, + sup a { + padding: 0 0.1em; + } + sub { + bottom: -0.25em; + } + sup { + top: -0.5em; + } + /* 代码相关的字体大小统一 */ + code, + kbd, + pre, + samp, + pre tt { + font-family: monospace; + font-size: 1em; + } + /* 去除默认边框 */ + fieldset, + img { + border: 0; + } + /* 图片初始化样式 */ + img { + border-style: none; + max-width: 100%; + box-sizing: content-box; + margin: 0 auto; + background-color: var(--color-canvas-default); + } + /* 可附标题内容元素的间距 */ + figure { + margin: 1em 40px; + } + /* 间隔线 */ + /* 一致化 horizontal rule */ + hr { + box-sizing: content-box; + overflow: hidden; + background: transparent; + border-bottom: 1px solid var(--color-border-muted); + height: 0.25em; + padding: 0; + margin: 24px 0; + background-color: var(--color-border-default); + border: 0; + &::before { + display: table; + content: ''; + } + &::after { + display: table; + clear: both; + content: ''; + } + } + + /* 表单元素并不继承父级 font 的问题 */ + button, + input, + select, + textarea { + font: inherit; + margin: 0; + overflow: visible; + font-family: inherit; + font-size: inherit; + line-height: inherit; + } + /* 外观显示为按钮 */ + [type='button'], + [type='reset'], + [type='submit'] { + -webkit-appearance: button; + } + /* 这两个表单样式规则覆盖 */ + [type='checkbox'], + [type='radio'] { + box-sizing: border-box; + padding: 0; + } + /* 数字按钮内部高度自动 */ + [type='number']::-webkit-inner-spin-button, + [type='number']::-webkit-outer-spin-button { + height: auto; + } + /* 搜索按钮内图标外观去除 */ + [type='search']::-webkit-search-cancel-button, + [type='search']::-webkit-search-decoration { + -webkit-appearance: none; + } + /* 输入框的占位符样式 */ + ::-webkit-input-placeholder { + color: inherit; + opacity: 0.54; + } + /* 文件选择按钮样式统一 */ + ::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit; + } + /* 占位符显示统一 */ + ::placeholder { + color: var(--color-fg-subtle); + opacity: 1; + } + /* table内的td,th去除留白 */ + td, + th { + padding: 0; + } + /* 伸缩框鼠标显示 */ + details summary { + cursor: pointer; + } + + details:not([open]) > *:not(summary) { + display: none !important; + } + /* 按键显示 */ + kbd { + display: inline-block; + padding: 3px 5px; + font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, + Liberation Mono, monospace; + line-height: 10px; + color: var(--color-fg-default); + vertical-align: middle; + background-color: var(--color-canvas-subtle); + border: solid 1px var(--color-neutral-muted); + border-bottom-color: var(--color-neutral-muted); + border-radius: 6px; + box-shadow: inset 0 -1px 0 var(--color-neutral-muted); + } + .clearfix:before, + .clearfix:after { + content: ''; + display: table; + } + .clearfix:after { + clear: both; + } + .clearfix { + zoom: 1; + } + /* 强制文本换行 */ + .textwrap, + .textwrap td, + .textwrap th { + word-wrap: break-word; + word-break: break-all; + } + .textwrap-table { + table-layout: fixed; + } + ul { + margin-left: 0; + padding-left: 0; + margin-left: 2em; + list-style: disc; + } + ol { + margin-left: 0; + padding-left: 0; + margin-left: 2em; + list-style: decimal; + li { + padding-left: 0.4em; + } + } + li + li { + margin-top: 0.25em; + } + li { + ul { + margin-bottom: 0.8em; + margin-left: 2em; + list-style: circle; + li { + ul { + list-style: square; + } + } + } + ol { + margin-bottom: 0.8em; + margin-left: 2em; + } + } + .task-list-item { + list-style-type: none; + position: relative; + > input { + &:nth-child(1) { + margin-right: 6px; + } + } + label { + font-weight: 400; + } + .handle { + display: none; + } + input[type='checkbox'] { + width: 0.9em; + height: 0.9em; + position: absolute; + left: -1.3em; + top: 0.35em; + } + } + .task-list-item.enabled { + label { + cursor: pointer; + } + } + .task-list-item + .task-list-item { + margin-top: 3px; + } + .contains-task-list { + // margin-left: 0.6em; + &:dir(rtl) { + .task-list-item { + input[type='checkbox'] { + margin: 0 -1.6em 0.25em 0.2em; + } + } + } + } + + .toc{ + margin-left: 0; + } + + dl { + display: block; + margin-block-start: 1em; + margin-block-end: 1em; + margin-inline-start: 0px; + margin-inline-end: 0px; + unicode-bidi: isolate; + dt { + display: block; + unicode-bidi: isolate; + } + dd { + display: block; + margin-inline-start: 40px; + unicode-bidi: isolate; + } + } +} diff --git a/app/composables/usePageTitle.ts b/app/composables/usePageTitle.ts new file mode 100644 index 0000000..9f27845 --- /dev/null +++ b/app/composables/usePageTitle.ts @@ -0,0 +1,28 @@ +import type { MaybeRefOrGetter } from 'vue' +import { toValue } from 'vue' + +/** + * 统一设置 document title:`片段 · … · 站点名`(站点名来自后台 `siteName`)。 + */ +export function usePageTitle( + parts: MaybeRefOrGetter, +) { + const { siteName } = useGlobalConfig() + + useHead(() => { + const raw = toValue(parts) + const list: (string | null | undefined)[] = Array.isArray(raw) + ? [...raw] + : [raw] + + const cleaned = list + .map((s) => (typeof s === 'string' ? s.trim() : '')) + .filter((s) => s.length > 0) + + const site = siteName.value.trim() || 'Person Panel' + const prefix = cleaned.join(' · ') + return { + title: prefix ? `${prefix} · ${site}` : site, + } + }) +} diff --git a/app/pages/@[publicSlug]/about/index.vue b/app/pages/@[publicSlug]/about/index.vue index 112a35b..37cb7d4 100644 --- a/app/pages/@[publicSlug]/about/index.vue +++ b/app/pages/@[publicSlug]/about/index.vue @@ -31,9 +31,9 @@ const displayName = computed( () => data.value?.user.nickname || data.value?.user.publicSlug || slug.value, ) -useHead(() => ({ - title: data.value ? `${displayName.value} · 关于` : '关于', -})) +usePageTitle(() => + data.value ? [displayName.value, '关于'] : [slug.value, '关于'], +)