NewsPicks トピックスと投稿機能

はじめに

こんにちは、NewsPicksの松本です。現在はTopics Product UnitでNewsPicksトピックスのプロダクト開発をしております。 さっそくですが、 2021年11月1日より【新サービス】NewsPicksトピックスが始まりました! 今回はトピックスのリリースへ向けてチームで取り組んだこと、トピックスの機能としてのポイントとなった投稿に関して少しご紹介できればと思います。

トピックスとは

NewsPicksトピックスとは、ニュースを起点としなくても、みなさんが自由にテーマを定め、発信をし、コミュニティを形成できる新しいサービスになります。

専門誌でもカバーしきれないような実務知や、身の回りで起きている小さな胎動、何時間でも話すことができるような偏愛。そうした知見を、もっと自由に、もっと闊達に、みなさんに発信していただける新しい土台を目指し日々開発を進めています。

newspicks.com

newspicks.com

newspicks.com

newspicks.com

newspicks.com

newspicks.com

是非こちらのトピックス一覧から、各トピックスを読んでみてください!

トピックスのリリースへ向けてチームで取り組んだこと

要件定義、設計、開発などはもちろんですが、 チーム力を高めたり、よりよいプロダクト開発が行えるように実施したポイントや方針を共有させてください!

3-1. 週1でオンラインでランチしながら雑談をする会

NewsPicksのプロダクトチームはシャッフルランチという取り組みをしています。
シャッフルランチというのは、別チームメンバーとランダムでオンラインランチの予定をくんで楽しく雑談をする会のことです。普段の業務ではあまり話さないメンバーとも話ができますし、毎回楽しく参加しています!
トピックスチームはシャッフルランチとは別でチーム内でも週一回ランチをしながら雑談をしています。最近の興味や趣味、勉強していることなど面白い話が聞けますし、当初よりメンバー間で気軽に会話ができるようになり意見交換が盛んになったように感じます。ランチ会で、生い立ち自己紹介をやってみたことがあるのですが、人となりが分かりとても楽しかったです。

3-2. 新しいコードは全てKotlinで

NewsPicksはServer side Kotlin を採用し、Javaで書かれている箇所もKotlinで書いていくという方針になっています。トピックスに関しても新規に作成するコードはもちろん既存コードもどんどんKotlinにリプレースという方針で開発を行いました。

tech.uzabase.com

3-3. 改善案があれば柔軟にやり方を変えてみる

レトロスペクティブでチームメンバーから上がった改善案などは積極的にみんなで試してみて特定のやり方にこだわらないようにしてきました。実際にやってみたTryについても振り返りを行なっています。

トピックス投稿機能

ここからは、トピックス機能の中でもポイントとなった機能の1つである「投稿機能」に関してライトに共有できればと思います。

トピックスでは、オーナーと呼ばれるトピックスの管理者やオーナー同様に記事投稿ができるモデレーターの方々が日々記事を投稿しており、NewsPicksからエディター画面へ遷移できるようになっています。(現在Webのみで利用可能となっています。)
投稿された記事は、トピックス一覧画面やニュースタブに表示されます。 エディターでは装飾やメニューの選択肢は出来る限り減らし、ゆとりのある行間・読みやすいテキストサイズなど、書き手が迷わず気持ち良く書ける UI を重視し、WYSIWYGを採用しました。

エディター画面

① 記事投稿・編集
記事投稿・編集はトピックスオーナー・モデレーター(編集者)のみが使用できる機能になっています。トピックスへの投稿用エディターを用意しており、そちらから記事作成や編集を行いトピックスへ投稿することができます。

エディター側では、文章や画像などをブロックという単位で管理し、入力された記事本文をJson形式で出力しデータ保存を行っています。画像などの一部のコンテンツデータは、記事本文とは非同期に保存し、リソースサーバーへのリンク情報をJsonデータに持たせています。

エディタ内でのブロック情報

記事更新の際も、記事データをJson形式でサーバーから受け取りクライアント側でパースしエディター上に表示します。

以下はエディターから出力されたデータです。blocksに各ブロック情報を持たせ管理しています。

{
  "time": 1645174982011,
  "blocks": [
    {
      "id": "xy5groSZb8",
      "type": "header",
      "data": {
        "text": "トピックスとは",
      }
    },
    {
      "id": "Vy8klBG5Ar",
      "type": "paragraph",
      "data": {
        "text": "NewsPicksトピックスとは、ニュースを起点としなくても、みなさんが自由にテーマを定め、発信をし、コミュニティを形成できる新しいサービスになります。"
      }
    },
    {
      "id": "7BKFGGWNwt",
      "type": "paragraph",
      "data": {
        "text": "専門誌でもカバーしきれないような実務知や、身の回りで起きている小さな胎動、何時間でも話すことができるような偏愛。そうした知見を、もっと自由に、もっと闊達に、みなさんに発信していただける新しい土台を目指し日々開発を進めています。"
      }
    },
    {
      "id": "4xdQzJCf80",
      "type": "image",
      "data": {
        "file": {
          "url": "https://*******/images/20220215144010617_jM2EQXFL.png"
        },
        "caption": "キャプション文言"
      }
    }
  ],
  "version": "2.22.2"
}

② 記事閲覧
ユーザーはWebアプリ、モバイルアプリから投稿されたトピックス記事を読むことができます。こちらの表示は、保存しているデータをバックエンドでHTML変換しクライアントへ返却しています。

バックエンドでの変換処理
保存されている文章や画像リンク情報はブロックという単位で管理し、ブロック種類に応じて指定のHtml形式への変換を行なっています。

/**
 * ブロック情報(サンプル)
 */
class BlockData {
    var id: String? = null
    lateinit var type: String
    lateinit var data: Map<String, Any>
}

/**
 * Html形式の情報(サンプル)
 */
data class HtmlData(val html: String = "", val style: String = "")

データ変換はBlockの種別毎にBlockConverterの実装クラスを作成し変換処理を行っています。 新規にエディター機能を追加し、ブロック種別が追加になる際もこちらの実装クラスを追加しServerでの変換処理を行うようにしています。

/**
 * editorの blockデータを HTML形式 に変換します。
 * サンプル(説明用)
 */
interface BlockConverter {

    val type: String
    
    val className: String get() = "$type-block"

    fun isApplicable(block: BlockData): Boolean {
        return type == block.type
    }

    fun toHtml(block: BlockData): HtmlData
}

/**
 * 以下のようなParagraph blockを HTML形式に変換するサンプル
 *
 * {
 *    "type": "paragraph",
 *    "data": {
 *      "text": "専門誌でもカバーしきれないような実務知や、*** "
 *    }
 * }
 *
 */
class ParagraphBlockConverter(override val type: String = "paragraph") : BlockConverter {

    override fun toHtml(block: BlockData): HtmlData {
        val text = block.data["text"] as? String ?: ""
        val html = """
            <p class="$className">
                ${text}
            </p>
        """.trimIndent()
        return HtmlData(html, style())
    }

    private fun style(): String {
        return ** 省略 **
    }
}

各ブロック毎に変換したHtml形式のタグ情報を集約してクライアントへ返却しています。

おわりに

今回は去年リリースされたトピックスと投稿機能に関してご紹介させていただきました。

投稿機能では、あらゆるコンテンツを内部で埋め込める拡張性を持たせていますが、書き手がより快適に表現できるようなプラグインはまだまだ足りてませんし、投稿機能に限らず改善についてもやりたいことだらけです。 チームとしてもより良いプロダクト開発ができるように日々挑戦をしていくつもりです!

今回お話させて頂いたことやNewsPicksに興味のあるエンジニアの方は是非カジュアルにお声がけください!

NewsPicksについて、組織体制について、技術スタック、チーム毎の取り組み、トピックスについて、などなんでも説明させて頂きます! カジュアル面談で是非お会いしましょう!

選考に興味がある方も是非!

apply.workable.com

最後まで読んでいただき、ありがとうございました!

Page top